This thread looks to be a little on the old side and therefore may no longer be relevant. Please see if there is a newer thread on the subject and ensure you're using the most recent build of any software if your question regards a particular product.
This thread has been locked and is no longer accepting new posts, if you have a question regarding this topic please email us at support@mindscape.co.nz
|
Good morning, I'm trying to create a "scrub" control for the property grid. For example, if I have a number in the grid, there would be a button or icon of some kind on the right hand side of the row that, if the left mouse button were held down on it, would capture the mouse and increment or decrement the value in the row based on the mouse's X movement. When the mouse button was released, the mouse would be released as well. In my application, values in the grid affect on screen elements in real time, so this kind of scrubbing would allow users to easily get the elements approximately where they want them before "fine tuning" by entering numbers manually. I know this is well beyond the intent of the property grid, and I'm not asking for an exact code example or anything, maybe just a push in the right direction. If I get it working I'll post the implementation here. Thanks! - Mike - |
|
|
That sounds like quite a cool little control, if what I'm visualising is the same as what you're visualising. I would start by looking at the Thumb control. The nice thing about Thumb is that it has in-built support for mouse capture, dragging and release. So my plan of attack would be something like this: * Create a WPF custom control with a dependency property of the appropriate type (double?) named Value. You could derive directly from Control or ContentControl, or possibly from RangeBase (which already provides the Value property). Value is the property you will databind when you finally come to use this control in a Property Grid editor. * For the default control template, use a Thumb. You will probably want to retemplate the Thumb to display some content from the custom control rather than its default appearance (which I think is that of a scroll bar thumb). [If you derive from ContentControl this should be easy to do using a ContentPresenter.] (You could also consider having a TextBlock whose Text was set to {TemplateBinding Value}, so that the user could see the value while they were changing it.) * Handle the DragDelta and DragCompleted events of the Thumb to update the Value property based on the X movement. You may also want to show an Adorner while a drag is in progress, so that the user can see that the mouse is still "attached" to the scrub control. I haven't tested this approach and clearly this is only an outline, but hopefully points you in the right direction. No warranties express or implied, though! |
|
|
Hi Ivan, I think I've *almost* got this working. I've got a class called ScrubControl that inheirits from UserControl and INotifyPropertyChanged. I create a dependency property in that class as follows: public static DependencyProperty ValueProperty = DependencyProperty.Register( Next, I created a DataTemplate like so: <DataTemplate x:Key="ScrubbedNumberEditor"> The ScrubbedNumberConverter is a modified version of your FeetAndInchesConverter that really just converts a double to a string in the Convert function and a string to a double in the ConvertBack function. I've placed breakpoints in those functions and the Convert function is called, but ConvertBack function is NEVER called no matter how much I change the property. I've also confirmed that the Value property's "set" is being called whenever the number changes. I think this is where it's breaking but I have no idea why it's not calling ConvertBack. I'm probably just doing something wrong with my bindings. Finally, here's my implementation of the PropertyGrid: <ms:PropertyGrid Name="propertyGrid" AllowModifyCollections="False" IsToolBarVisible="False"> Any ideas? Thanks for all your help. - Mike - |
|
|
I should also mention that the initial number for each property is correct, that is, it is being properly set in the ScrubControl from the source dictionary. It seems like the binding is only working one way.
|
|
|
Update! I added Mode=TwoWay... <DataTemplate x:Key="ScrubbedNumberEditor"> Now I actually do get the dictionary's CollectionChanged event when I change the property in the grid, but then the oddest thing occurs. When the SetFloatValue function below is called... I step into it and it goes into the "set" of the Value property in the ScrubControl! I even set a breakpoint in SetFloatValue and it never hits. I feel like I must be doing something very very wrong... I'm calling one function and it's executing something completely unrelated. The PropertyReader has no idea what the ScrubControl is. void onPropertyDictionaryCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) // ......
Totally confused, but still going! - Mike -
|
|
|
So I found out that it has nothing to do with the SetFloatValue call, I added a Console.WriteLine call just above it to get some more information and it does the same thing. Now Console.WriteLine is never called. It just seems like the program doesn't care much about what's in the CollectionChanged event handler, it just decides to redirect the program flow to the "set" for the Value property. I've also confirmed that it's not switching threads, the switch from the CollectionChanged event handler to the "set" for the Value property both happen on the main thread. More info... I stepped through the code starting at the MouseMove event handler where it sets the Value property to whatever it should be based on the mouse X delta (Value = Value + delta) and it gets into the this.SetValue(ValueProperty, value) call, which eventually will call the CollectionChanged event handler. It just seems like the event handler returns early for no apparent reason. |
|
|
Ok I feel embarassed now, the answer is so simple. In the CollectionChanged event handler, I was casting the object "value" into a float, but apparently a double cast to a float will throw an exception. :3 I guess the SetValue method has some kind of exception handling that just caught the exception and threw it away, so I didn't know that the problem was there until I used my own try/catch in the CollectionChanged event handler. It looks like this is going to work great. I'll clean up my code a bit and post the final implementation. |
|
|
Yes, casting boxed values is tricky -- you must always cast to the actual type first, and can then cast to another value type if required, e.g. (float)(double)value. I hesitate to pass comment on something that is now working but there is one detail you need to be aware of which may explain some of the quirks you're seeing around SetValue (e.g. why your exception handling isn't propagating to the setter): WPF doesn't call your setter. Sounds weird? The way that dependency properties work is that WPF identifies them by "slot" (name/DP object) and updates their "slots" directly. You can think of this as WPF calling SetValue directly rather than calling the CLR wrapper property setter. The CLR property (Value) isn't actually required at all -- it is conventional so that other objects can get and set the value in the normal way. Therefore, when you implement a dependency property with a CLR wrapper, your code MUST only ever include the GetValue and SetValue calls. You must NOT include additional logic in the getter or the setter because that additional will only run if the properties are got or set from user code (that goes via the CLR wrapper property). Hopefully a future version of FxCop will provide a check for this -- I don't know a single person who hasn't fallen into this trap at some point! So how do you associate custom change logic with a DP? You set it up when you register the DP. DependencyProperty.Register takes an optional PropertyMetadata argument, so you can set up your DP like this: public static DependencyProperty ValueProperty = DependencyProperty.Register( The framework guarantees that OnValueChanged will be called whenever the Value property changes. By the way, the metadata also allows you to specify that the property binds two-way by default, which saves you and your users having to remember to put Mode=TwoWay on the Binding. Final stylistic note: instead of explicitly updating textBox.Text, it is probably better just to bind textBox.Text to the Value property. You should also normally not need to raise PropertyChanged for DPs because WPF will propagate changes to bound elements (WPF can detect changes to DPs because WPF "owns" the slots where DP data is stored, unlike CLR properties which are opaque to WPF) -- you would only need to do this if something needs to be able to monitor the control for state changes but can't do so by data binding. (And you probably don't need to do this because you are rightly handling data updates at the data level, in CollectionChanged, rather than at the UI level.) Therefore you probably don't need an OnValueChanged handler at all! As I said, I'm not saying this to get you to change your working implementation -- just passing on some tips and background! |
|
|
Ivan, Thanks for the tips! I changed my property setter and added the framework metadata as you mentioned. Here's my final implementation. Note that the image sources used are just pictures of an arrow, the normal one is black and the active one is blue so that users can see when they hover over it. This would probably be better implemented in xaml, but it's already working and my timetable is tight. :) Also you'll see PropertyGrid.scrubResolution which is not the Mindscape PropertyGrid, but my own PropertyGrid usercontrol that contains a Mindscape PropertyGrid. Those are just static properties that are set when the SelectedGridItemChanged event takes place so that each property can have it's own resolution, minimum, and maximum. There are still some tweaks that need to be made, but at least it's working as is. Thanks a lot for your help! /************************** ScrubControl.cs **************************/ The X position shows what the hover image looks like. If you were to click and hold anywhere on the arrows and move the mouse back and forth in the X direction, it will change the property. :) Thanks again Ivan, I appreciate all your help.
|
|