I'm using the Telerik WPF RadGanttView control to display a bunch of data. Since the data can be arbitrarily weird - events that are instantaneous, or that last days, or have a week between them - it's not possible to set a PixelLength (i.e. a scale factor) that's guaranteed to be useful. I've been asked to make it possible to change this scale factor with a slider. Unfortunately, just changing the scale factor with the slider has a usability issue, which I need to fix by manually adjusting where the view is scrolled. I'm at a loss as to how exactly.
To outline the usability problem I'm fixing: the view works by providing a "viewport", located at an "offset", over the whole "extent" of the data set. (Terms lifted from the Telerik API. These seem to simply represent pixels in the canvas underlying the scrollable view.) When the scale factor goes from, say, 100% to 200% (which corresponds to halving the PixelLength), what happens is that the width of the extent is doubled, but the horizontal offset remains the same. The consequence is that after zooming in, it's quite likely you'll see entirely different data than before, since the events that were there before got "pulled out" of the viewport to the right.
The way I intend to fix this is: grab the offset/viewport/extent etc before zooming in, zoom, then do some maths I haven't figured out yet with that and the new offset/viewport/extent. The problem is: the properties of RadGanttView that describe the scrolling stuff are not DependencyPropertys, and I cannot simply bind them to properties on my ViewModel. (In fact, they're not even accessible in XAML to begin with, RadGanttView implements IScrollingInfo explicitly.)
So, my question is: how do I, in my ViewModel, or wherever else in reaction to the ViewModel's scale factor changing, access properties of a control in the corresponding View, that cannot be data-bound? Every search I tried tells me that accessing the view from the viewmodel is "not MVVM", but since Telerik is a third-party library, I can't really refactor how this works on their side.
A fill-in-the-blanks outline of my code:
FooViewModel.cs
class FooViewModel
{
// A slider in the view pushes its value into this property
double ScaleFactor
{
get { /*...*/ }
set
{
PixelLength = GetNewPixelLength(value);
// ...
}
}
// The RadGanttView pulls its scale from this property
double PixelLength
{
get { ... }
set
{
// How do I get these values?
var oldOffset = ???;
var oldExtent - ???;
// Trigger the actual change in the view.
PropertyChanged("PixelLength", ...);
var newExtent = ???;
???.HorizontalOffset = GetNewOffset(...);
}
}
}
FooView.xaml
<UserControl ... d:DataContext="{d:DesignInstance my:FooViewModel}">
<telerik:RadGanttView x:Name="Gantt">
<!-- ... -->
</telerik:RadGanttView>
</UserControl>
Things I've looked into:
Making a bunch of DependencyProperty wrappers in the FooView code-behind that just access corresponding properties in the RadGanttView. This seems like it both is a horrible abuse of the system - i.e. it doesn't seem to make sense to have a dependency property not backed by a DependencyObject. And also that it plain wouldn't work - in WPF, the view seems to "push" data into the view model, and I'd still have no way to actually get the current values, since the values of the wrapper properties would never get updated.
Uh, Commands, maybe? I'm fairly new at WPF, I have no clue how those work at all, merely a vague impression that they might be a loosely coupled way for the view model to talk to the view.
Attached properties? Custom bindings? Way above my pay grade, if they help I don't know how myself. It seems like they could accomplish the "dirty" solution of just binding a control to a view model property. Since the type of that property would be IScrollingInfo, not the whole view, I could live with that.
Attached Behaviors may solve your issue. They are basically Attached Properties with a callback.
Check out my answer here. Just instead of KeyDown event, you register to the Changed event (or whatever your control is actually calling it) and then assign the value you get from the Changed event to your Attached Property, and you have two way binding on a non-bindable property
Related
I'm stepping through the code and I see that in the constructor of my view, I set DataContext to a new instance of my view model. Stepping into it, I can see that the field Thingies is set. Stepping out back to the view's constructor, I can verify that, indeed, the property of the view model is set and the count of the elements is correct.
Then... (let's get back to this spot in a second)
Finally, I can see the rendered GUI where the data produced by the view model is shown just as supposed to. Everything has worked out as supposed to. Now, I only need to set the first row of the grid as selected, which shouldn't be a problem, because I know that the data is there (verified by a step-in to model view's constructor, watch on data context's fields and the actual window's contents).
But nooo... (now, let's get back to the aforementioned spot)
When I investigate the grid's DataItems property, while still in the constructor, I discover that there's zero elements in it. Since I've seen those on the screen, I know they must get there somehow but, apparently, the binding of the view model's property Thingies takes place after the constructor's run.
I'd like to enforce the binding to take place while still in the constructor (if that's the most appropriate approach). As the plan B, I'm thinking about reacting to some event (like Blopp_OnDataBound(...) or something) but I haven't found anything except target being updated. I've put TargetUpdated="TheGriddy_OnTargetUpdated" next to the data source's binding but the method doesn't invoke.
NB. Even though I'm doing this on a specific control, I'm convinced that the question is generally applicable to any control as it's about the XAML/WPF's data binding paradigm. Correct me if I'm wrong. (I'm on Infragistics' XamDataGrid but the behavior is most likely reproduceable for the good, old, plain DataGrid.)
As requested, I'm also providing a small sample, which I chose not to before, as I thought it wasn't of any help. I stand corrected.
public MyView()
{
InitializeComponent();
DataContext = new MyViewModel();
if (xamDataGrid.DataSource != null)
throw new HappyException("yes!");
}
private void SomeMethod()
{
if (xamDataGrid.DataSource != null)
throw new HappyException("yes!");
}
The problem is that the exception is never thrown in the constructor but is
in the method. The data source is set as we bind, so the binding must occur after the constructor's been run, right?
I need to select the first row. That can be done when the first row exists...
Data binding seems to happen during the LayoutUpdated event when the window is first shown. I think the plan B is the only possible way so far. Use the TargetUpdated event as mentioned in the How to detect data binding completed in WPF thread, but note that you need to explicitly enable NotifyOnTargetUpdated otherwise it will not fire (as you already have seen).
I guess I'm a little confused as to whether properties like display range should be placed in the model (that gets inherited as a datacontext so that subcontrols can bind to it easily)or whether I should have properties be placed in the graphviewer class, and then let the components that need access to it have their own properties that they bind to the ancestor instead. Is it cleaner to bind to an ancestor control or just to bind off the model? I feel like the latter is cleaner, but then display range is pretty clearly a property of the view.
For example. I have a property AxisdivisionUnit that is needed in a scrollviewer, as well as used by a few thumbs to recalculate position on graph updates. The scrollviewer only appears when a treeview in the top level control (graphviewer) is populated. So I could either put the property axisdivisionunit on the graphviewer and bind the property to properties in the scrollviewer and thumb. Or I could have the thumb and scrollviewer bind to properties in the model (viewmodel if i were better at separating the UI out entirely.
Let me see if I can help..
First off, since you are discussing mainly the presentation of what things look like on your UI, then I do not think that the property should be in your model at all. The real question is whether it belongs in your View or ViewModel.
AxisDivisionUnit, sounds like it is only part of how the graph looks. I'm thinking that it would make more sense for that to be in the view only. If you had some properties describing the limits of your graph that were tied to business logic, then something like that may be better off in the ViewModel since you could possibly want to test that code and if you were to replace the UI you'd still want to enforce those exact same limitations.
I guess ask yourself, "If I were to replace this graph with a totally different graph and UI to display the same data, would I have to enforce this same logic?" If the answer is no, that it is just how you want to display it for this case... then it belongs in the View and you can bind a Control's property to another control's property or use triggers, behaviors, etc. to implement it in the View.
I'm currently writing a WPF progress bar that includes a rate (see Windows 8 - Fancy Progress Bars API?).
The screenshot below shows what I've got so far (left) and a badly done all in code as part of my learning exercise (right). I'm now trying to convert the code version to use as much XAML as possible.
I've got most of the way there by creating a new class called RateBase and implementing it in a similar way to RangeBase. I've then added a new instance and provided a template file RangeGraph. I'm attempting to do this as by the book as possible, but I'm not sure how to tackle the final stage.
I now wish to add a graph, this graph is to display the rate as it has changed historically as the progress has progressed. I have 'Rate' as a value I can bind to, but I believe somwhere I need a Double[] containing my historical rate values. My question is where should this be placed (I don't really want to pollute RateBase) and how do I bind to it from my template (I don't believe I can bind to RangeGraph.cs if I add properties on there or am I wrong?)
You are right, you will need historical Data. In my opinion, whenever the bound Dependency Property Rate changes, you should move the old value into an IEnumerable that's defined on the graph control itself (The same place that has the DP) and use that to draw the lines. I personally would create a class named MyControlData and add an instance of that to the control.
You might also want to add a Timer and move the current Rate to the IEnumerable when it Elapses, so longer streaks of the same rate will appear as multiple bars. Depends on how you actually determine progress. You might get into the following dilemma here : The Rate changes at a different interval than the Percentage in most cases - what floats your boat?
Keeping the history in your control's scope leaves your application agnostic to the history of your Rate, but lets your control display it as required.
To use DataBinding in a UserControl, edit the <UserControl x:Name="myControl"> node in Control.xaml and add a name like shown here. Wherever you want to bind, refer to ElementName=myControl. Please note that you will have to implement INotifyPropertyChanged on the Control (or on MyControlData) if you want to achieve this - or, and that would be advisable, directly implement it as a dependency property.
And BTW, if you have no idea how to achieve what you intend to have a look into ItemsControl. I think what you want to do can easily be achieved by means as simple as using ItemsControl and ItemsTemplate, where the ItemsSource is your historical data and the ItemTemplate depicts your current rate in comparison to your MaxRate. MaxRate is another property you can set from the DependencyProperty Rate's changed handler.
I sometimes think I maybe using Dependency Properties unnecessarily. When do I need to use it? When I have a property that dependes on other properties? Say I have a Color property that I want it to be dependent on properties Hue, Saturation, Luminosity do I use a dependency property? Or what do I use? I controls thats bound to Color to update when properties Hue, Saturation, Luminosity are changed.
for now what I did was
public byte Hue {
get { return _hue; }
set
{
if (_hue == value)
return;
_hue = value;
NotifyPropertyChanged("Hue");
NotifyPropertyChanged("Color"); // to update controls bound to color
}
}
But I think this is not the right way of doing things? If I have more properties that affect color, I will have 1 extra line in all those properties?
You should only use a DependencyProperty when you want to be able to bind its value to something through XAML, e.g.
<local:MyObject MyDependencyProperty="{Binding ...}" />
Update: as mentioned by Ian below, dependency properties are also required if you want to be able to animate your property or set it through a style
If you do not need to work in this way then it is unnecessary. e.g. If you just want to be able to set the value to a constant through XAML (as below) this will work without using a DependencyProperty
<local:MyObject MyRegularProperty="Some Value" />
Similarly, if you want to bind to the value of a property on (for example) your view model:
<TextBlock Text="{Binding MyViewModelProperty}" />
then you do not need to use a DependencyProperty. Provided that you implement INotifyPropertyChanged then the Text will still be updated when the property changes.
Edit: on re-reading your question, I am not sure whether or not your situation will be affected by whether or not you use a DependencyProperty - if I'm reading it correctly, all you want to do is cause a number of properties to be updated on the UI when any one of those properties changes, right?
I don't think there is anything wrong with how you are implementing things at the moment (i.e. raising a lot of PropertyChanged events in each setter), but if you aren't keen on in then you could try having a single property that exposes relevant child properties to bind to that are all calculated:
class ColorWrapper
{
public Color Color { get; set; }
public byte Hue
{
get { return this.Color.Hue; } //or however this is calculated
}
Then have a Color property on your ViewModel that raises the PropertyChanged event and bind to that through the View:
<TextBlock Text="{Binding Color.Hue}" />
As I said, I wouldn't say that this is particularly an improvement on what you have already though.
The general rules are:
For XAML controls, use dependency properties;
For data (which you bind to in the interface), use INotifyPropertyChanged.
There are exceptions, but they are rare.
Another use of dependency properties is with navigation journal. Custom dependency properties on a Page with Juornal flag in the meta-data are included in the state that WPF saves for the page.
Remember that Dependency Properties, although they allow Binding either as a source or a target, are also Thread-Sensitive, and when serializing you will have to use a surrogate, serialization as DependencyObject isn't serializable.
Oh, and Equals and GetHashCode are sealed :(
Another usage of DP is Attached Property. Attached property is a type of DependencyProperty where the property can be used in any other controls.
For example, you can declare AttachedProperty in class MyAttachedProperties and used it for TextBlock, Button, Label,...
Some examples of Attached Properties: Grid.Row, Grid.Column. A customized one:
public static readonly DependencyProperty DarkThemeProperty =
DependencyProperty.RegisterAttached("DarkTheme", typeof(Theme), typeof(Page));
public static Theme GetDarkTheme(DependencyObject obj)
{
return (Theme)obj.GetValue(DarkThemeProperty);
}
public static void SetDarkTheme(DependencyObject obj, Theme value)
{
obj.SetValue(DarkThemeProperty, value);
}
I was stepping into the "trap" of DependencyProperty often.
At some point I realized that every single Change of any value is triggered somehow and somewhere, that can be an algorithm, a process, a user-input... and a lot of things, but NEVER it just changes by itself.
Get control about that triggers, hook directly at source and you never need DependencyProperty and even no INotifyPropertyChanged. I mean maybe I do something wrong or right, but all problems, and they are sometimes of serious difficulty, I solve with only RoutedEvents and Properties { get , set } -> And inside of get, set you can do SO much. Everthing that cannot be solved here, can be done with AddHandler and don't forget to RemoveHandler when not needed anymore.
Also needed is a solid understanding of Threading and Dispatching in some cases.
Also needed is a good knowledge how to work with Lambda expressions and Actions.
(Probably not useable for windows phone etc. applications, guessing)
Respectively: A serious drawback is coding control and effort. For that approach I need to write more Code and have better documentation, the big advantage is better understanding what is actually happen within your application and what could happen, what are the extends. There is NO drawback in performance, it is even faster, because when you hook directly into your triggers, you are using the lightest and most appropriate form of an Approach - Executing Code ONLY when it is necessary and only that heavy that it is necessary.
Finally: DependencyProperties could make sense in very responsive applications where you actually DON't KNOW the possible relations and reactions. That could be over a million possible cases -> You don't want to have control over those, you would never finish your product.
Caveats: Not everything that looks like it is producing a ton of unknown cases are unknown. You can work with RANGES (from x to y) and write algorithms to regain control over thousands of possible cases with just one method.
So that are my two cents: DependencyProperties and INotifyPropertyChanged is not needed in most of the cases and just a way of having an easier coding life at the cost of actually understanding your product and (sometimes) performance.
(Notification to myself: I write this because I just came into a situation where I was thinking I need DependencyProperty, this time for real.. but no.. one hour of thinking and the solution was easy, found the trigger.)
I'm a bit confused as to what a viewmodel's role is beyond databinding. I have a menu built in silverlight. The menu has x number of menu items which is determined at runtime. One of the features I would like to add to this is that each menuitem has a different text colour when hovered over.
Is it the role of the view to have a colour selector method or should the view handle this in it's code behind?
Normally I would keep the coloring/styling in XAML if possible - My view of the ViewModel is that it is responsible for providing all the data (ie. not graphical stuff) from the Model in a manner the View can consume.
If it was complex logic that determined the color and it was to be reused - I might be tempted to put it in the ViewModel tho.
The view model is used by the data binding process as a "safe" way to allow you to sort/filter/group the records as seen by a specific control without (necessarily) making changes to the actual bound data set (that is, unless/until you tell it to). (FMI read Bea's article here.)
I agree with Goblin here, in that the presentation aspects like color might be best kept separate in the XAML, for example in the DataTemplate used by that control.