I'm relatively new to WPF and Behaviors.
I have this behavior, I need to execute DoSomething() every time I set IsRedundant in the ViewModel.
Each time I need to trigger DoSomething, I would need to change the value of the property and this is confusing (if ture => set it to false, If false => set it to true). IsRedundant only used to raise the property changed event and for nothing else.
Is there a better way of achieving this ?
Any ideas ?
wpf
<i:Interaction.Behaviors>
<local:UIElementBehavior Redundant="{Binding IsRedundant, Mode=TwoWay}"/ >
</i:Interaction.Behaviors>
C#
class UIElementBehavior : Behavior<UIElement>
{
public static readonly DependencyProperty RedundantProperty = DependencyProperty.Register(
"Redundant",
typeof(bool),
typeof(UIElementBehavior),
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, DoSomething));
public bool Redundant
{
get { return (bool)GetValue(RedundantProperty); }
set { SetValue(RedundantProperty, value); }
}
private static void DoSomething(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//Do something on the AssociatedObject
}
}
Each time I need to trigger DoSomething, I would need to change the value of the property and this is confusing (if true => set it to false, If false => set it to true)
The problem is that you are using binding. Binding required target to be dependency property. And those are special, their setters aren't called, so you have to use callback to get informed when their value is changed via binding.
Moreover there is internally a check if value is different, for performance reasons callback is not called if value is the same, so you must change it as you do already.
An alternative solution is to simply add event in the view model:
public class ViewModel: INotifyPropertyChanged
{
public EventHandler SomethingHappens;
// call this to tell something to listener if any (can be view or another view model)
public OnSomethingHappens() => SomethingHappens?.Invoke(this, EventArgs.Empty);
...
}
Now you can subscribe/unsubscribe in the view to/from this event and do something in the event handler. If you are purist, then refactor code from the view into reusable behavior.
Is it shorter? Nope. Is it more clear? Yes, compared to using bool and such "wonderful" code:
IsRedundant = false;
IsRedundant = true; // lol
I was using bool properties like you do to inform the view in the past.
Then I used events.
Now I use combination of both. Every view model already implements INotifyPropertyChanged so why not use it?
Think about IsRedundant as a state. It can be used not only to trigger some method, but also used by the view to run animations via data triggers, control visibility of dynamic layout, etc. So you need a normal bool property in view model.
The view then can subscribe to/unsubscribe from PropertyChanged and simply have to check:
if(e.PropertyName == nameof(ViewModel.IsRedudant)) { ... }
Related
I'm struggling to understand the usage of delegate commands (from Prism) and I build a dummmy application in which I intend to do the following.
I have the command as
private readonly DelegateCommand selectAll;
public ICommand SelectAll
{
get { return selectAll; }
}
and use it as
selectAll= new DelegateCommand(SelectAll,CanSelectAll);
private bool CanSelectAll()
{
if (AllSelectedItems.Count()>3)
{
return true;
}
return false;
}
public IList<Student> AllItemsSelected
{
get => m_Items;
set => Set(ref m_Items, value);
}
I can see the button being disabled as expected when my ViewModel gets initialized but after even though sometimes this AllSelectedItems.count > 3, it doesn't seem to update and notify the UI.
What am I doing wrong here?
When you create the command, tell it to observe the property AllItemsSelected, like this:
selectAll= new DelegateCommand(SelectAll,CanSelectAll)
.ObservesProperty(() => AllItemsSelected);
That will make the command's state update every time AllItemsSelected changes.
This function, ObservesProperty is a nice feature of Prism. It lets you set up one-time monitoring of all your properties on which that comand's state depends.
The CanSelectAll method is not called automatically when the collection changes, after all how should the command know when to reevaluate the the condition? You have to explicitly tell it to do so.
An ICommand exposes a CanExecutChanged event that must be raised to notify the element binding the command to call the CanExecute method in order to evaluate if the command can be executed or not. This usually enables or disables the element in the UI, e.g. a Button. When and how this event is raised depends on the concrete implementation of the ICommand interface.
In Prism for DelegateCommands, this can be done in two different ways.
Call the RaiseCanExecuteChanged on the command. This could be done in the setter of your AllItemsSelected property.
public IList<Student> AllItemsSelected
{
get => m_Items;
set
{
Set(ref m_Items, value);
selectAll.RaiseCanExecuteChanged();
}
}
Another way of doing this is using the ObservesProperty method when instantiating the command. You pass a lambda for the property to be observed and the command will automatically raise the CanExecuteChanged event once a PropertyChanged event is raised for it. That means this mechanism only works if your view model implements INotifyPropertyChanged and your property raises PropertyChanged.
selectAll= new DelegateCommand(SelectAll, CanSelectAll).ObservesProperty(() => AllItemsSelected);
Which mechanism you choose is up to you. For your specific case it is important to know how AllItemsSelected changes. If you always assign a new collection once the selection changes, the examples above will work, since then each time the setter of the property is called and PropertyChanged is raised and therefore ObservesProperty will pick up the change and call CanExecutChanged for example.
However, if you reuse the same collection, e.g. only add and delete items from it, this will not work, as the actual collection object does not change, which means no call to the setter and no PropertyChanged. In this case put the call to RaiseCanExecuteChanged into the method that adds, deletes or modifies the collection.
In case the collection is modified somewhere else e.g. items are added through the UI directly to the collection, you would have to use a collection type that supports notifying collection changes like ObservableCollection<T> (through the CollectionChanged event). You could add a handler to CollectionChanged which calls RaiseCanExecuteChanged.
public class MyViewModel : BindableBase
{
private readonly DelegateCommand _selectAll;
public MyViewModel()
{
_selectAll = new DelegateCommand(ExecuteSelectAll, CanExecuteSelectAll);
AllSelectedItems = new ObservableCollection<Student>();
AllSelectedItems.CollectionChanged += OnAllSelectedItemsChanged;
}
public ICommand SelectAll => _selectAll;
public ObservableCollection<Student> AllSelectedItems
{
get => m_Items;
set => Set(ref m_Items, value);
}
private void ExecuteSelectAll()
{
// ...your code.
}
private bool CanExecuteSelectAll()
{
return AllSelectedItems.Count > 3;
}
private void OnAllSelectedItemsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
_selectAll.RaiseCanExecuteChanged();
}
}
This is such a basic question, but I don't think I've done this before despite having bound so many properties. I originally was planning to bind a class called TimeScale to various objects.
In class A we have a dependency property that I want to call change notification on. However, change notification is not done manually through this class.
public TimeScale AxisTimeScale
{
get { return (TimeScale)GetValue(AxisTimeScaleProperty); }
set { SetValue(AxisTimeScaleProperty, value); }
}
public static readonly DependencyProperty AxisTimeScaleProperty =
DependencyProperty.Register("AxisTimeScale",
typeof(TimeScale), typeof(SignalPanel),
new FrameworkPropertyMetadata(new TimeScale()));
this binds to source class B
private class B
{
private TimeScale _GraphTimeScale;
public TimeScale GraphTimeScale
{
get { return _GraphTimeScale; }
set
{
if (value != _GraphTimeScale)
{
_GraphTimeScale = value;
OnPropertyChanged("GraphTimeScale");
}
}
}
}
Looking at it again I guess all I really want is to call propertychanged on a dependency property, but since I didn't implement Inotifypropertychanged, I am wondering how i do that.
I think DependencyObject already implements Inotifypropertychanged, so I have access to this:
OnPropertyChanged(new DependencyPropertyChangedEventArgs(property, old value, new value));
However, inserting the same object into both the old value and new value slots results in the PropertyChanged event not firing (I assume the implementation checks whether the two values are the same before firing the event). I want to avoid creating a new object if possible. I guess one option is to override OnPropertyChanged. Nope that also requires me to have a dependency propertychanged event args.
Update
OnPropertyChanged("TimeScale");
to
OnPropertyChanged("GraphTimeScale");
Or,
you can wrap the TimeScale class with an ObservableObject so that you can subscribe to object change events and raise them from there.
More info: http://msdn.microsoft.com/en-us/library/ff653818.aspx
Subscribe to the PropertyChanged notification of NumberOfUnits, and then raise OnPropertyChanged("GraphTimeScale") in the property changed event handler.
Would be interested if there is a better way though.
ViewModel:
public class MyViewModel : INotifyPropertyChanged
{
public string MyText { ... }
}
XAML:
<my:MySpecialTextBox Text="{Binding MyText}" />
Custom Control:
public class MySpecialTextBox : TextBox
{
static MySpecialTextBox()
{
TextProperty.OverrideMetadata(typeof(MySpecialTextBox),
new FrameworkPropertyMetadata
{
BindsTwoWayByDefault = true,
DefaultValue = string.empty,
PropertyChangedCallback = OnTextPropertyChanged
});
}
private static void OnTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = d as MySpecialTextBox;
if (control != null)
{
control.Text = SomeAdjustedValue((string)e.NewValue);
}
}
}
The problem is that while the DependencyProperty in the custom control does adjust properly, it does not update the ViewModel. I realize that this seems as if it should be a CoerceValueCallback due to the naming of SomeAdjustedValue, but Coercion does not change the ViewModel value either. I can't seem to update the value in my ViewModel if it was the trigger for the OnTextPropertyChanged callback to begin with... I did a debug trace and it does not go through the ViewModel a second time with the new value. Not sure what to do here to fix this.
In the FrameworkPropertyMetadata, there are different constructors you can use. Use one with the FrameworkPropertyMetadataOptions parameter. The FrameworkPropertyMetadataOptions.BindsTwoWayByDefault option will turn on two way binding by default, otherwise it's just one way.
Edit: So you did, I should stop trying to answer questions when I'm sick.
A disclaimer first- this smells like logic that should live inside of the ViewModel, not in the UI binding.
That said, if you are set on doing it this way, I think that you need to first check whether the "Adjusted Value" is different than the one already provided (to avoid looping indefinitely), then use DependencyProperty.SetValue to set the value of the dependency property on the control, rather than just setting it's Text property.
TextBox.Text is a binding, and you are replacing that binding with a string value in OnTextPropertyChanged, so the property is no longer bound to your datasource.
I think you would need to get the binding on TextBox.Text and update the source, however I wouldn't recommend doing that since you'd be mixing your Business Logic layer with your UI layer.
If you only want a display some custom formatting, I would do it in a Converter so it doesn't actually change your data source's value. If you want to change the actual data source value, I would do that with the ViewModel's PropertyChanged event
Did you try this?
private static void OnTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = d as MySpecialTextBox;
if (control != null)
{
control.SetCurrentValue(TextBox.TextProperty, SomeAdjustedValue((string)e.NewValue));
}
}
The SetCurrentValue() method ensures that the binding is preserved, where a simple SetValue() which is what is called under the hood if you use the Text property setter will remove any binding.
I have a simple usercontrol (WinForms) with some public properties. When I use this control, I want to databind to those properties with the DataSourceUpdateMode set to OnPropertyChanged. The datasource is a class which implements INotifyPropertyChanged.
I'm aware of the need to create bindings against the properties and I'm doing that.
I assumed that my usercontrol would have to implement an interface, or the properties would need to be decorated with some attribute, or something along those lines.But my research has come up blank.
How should this be accomplished? At the moment I'm doing it by calling OnValidating() in my usercontrol whenever a property changes, but that doesn't seem right.
I can get validation to happen if I set the CausesValidation to true on the usercontrol, but that's not very useful to me. I need to validate each child property as it changes.
Note this is a WinForms situation.
EDIT: Evidently I have no talent for explanation so hopefully this will clarify what I'm doing. This is an abbreviated example:
// I have a user control
public class MyControl : UserControl
{
// I'm binding to this property
public string ControlProperty { get; set; }
public void DoSomething()
{
// when the property value changes, the change should immediately be applied
// to the bound datasource
ControlProperty = "new value";
// This is how I make it work, but it seems wrong
OnValidating();
}
}
// the class being bound to the usercontrol
public class MyDataSource : INotifyPropertyChanged
{
private string sourceProperty;
public string SourceProperty
{
get { return sourceProperty; }
set
{
if (value != sourceProperty)
{
sourceProperty = value;
NotifyPropertyChanged("SourceProperty");
}
}
}
// boilerplate stuff
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public class MyForm : Form
{
private MyControl myControl;
public MyForm()
{
// create the datasource
var dataSource = new MyDataSource() { SourceProperty = "test" };
// bind a property of the datasource to a property of the usercontrol
myControl.DataBindings.Add("ControlProperty", dataSource, "SourceProperty",
false, DataSourceUpdateMode.OnPropertyChanged); // note the update mode
}
}
(I have tried this using a BindingSource, but the result was the same.)
Now what I want to happen is that when the value of MyControl.ControlProperty changes, the change is immediately propagated to the datasource (the MyDataSource instance). To achieve this I call OnValidating() in the usercontrol after changing the property. If I don't do that, I have to wait until validation gets triggered by a focus change, which is the equivalent of the "OnValidation" update mode, rather than the desired "OnPropertyUpdate" validation mode. I just don't feel like calling OnValidating() after altering a property value is the right thing to do, even if it (kind of) works.
Am I right in assuming the calling OnValidating() is not the right way to do this? If so, how do I notify the datasource of the ControlProperty change?
I think I've got this figured out. I didn't understand how change notifications were sent from control to bound datasource.
Yes, calling OnValidating() is the wrong way.
From what I've pieced together, there are two ways a control can notify the datasource that a property has changed.
One way is for the control to implement INotifyPropertyChanged. I had never done this from the control side before, and I thought only the datasource side of the binding had to implement it.
When I implemented INotifyPropertyChanged on my user control, and raised the PropertyChanged event at the appropriate time, it worked.
The second way is for the control to raise a specific change event for each property. The event must follow the naming convention: <propertyname>Changed
e.g. for my example it would be
public event EventHandler ControlPropertyChanged
If my property was called Foo, it would be FooChanged.
I failed to notice the relavent part of the MSDN documentation, where it says:
For change notification to occur in a
binding between a bound client and a
data source, your bound type should
either:
Implement the INotifyPropertyChanged
interface (preferred).
Provide a change event for each
property of the bound type.
This second way is how all existing WinForms controls work, so this is how I'm doing it now. I use INotifyPropertyChanged on my datasource, but I raise the Changed events on my control. This seems to be the conventional way.
Implementing the INotifyPropertyChanged interface is very simple. Here is a sample that shows an object with a single public field...
public class Demo : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
private string _demoField;
public string DemoField
{
get {return demoField; }
set
{
if (value != demoField)
{
demoField = value;
NotifyPropertyChanged("DemoField");
}
}
}
}
Then you would create a Binding instance to bind a control property to a property (DemoField) on your source instance (instance of Demo).
I have a lot of existing business objects with many properties and collections inside which I want to bind the userinterface to. Using DependencyProperty or ObservableCollections inside these objects is not an option. As I know exactly when I modify these objects, I would like to have a mechanism to update all UI controls when I do this. As an extra I also don't know which UI controls bind to these objects and to what properties.
Here is a simplified code of what I tried to do by now:
public class Artikel
{
public int MyProperty {get;set;}
}
public partial class MainWindow : Window
{
public Artikel artikel
{
get { return (Artikel)GetValue(artikelProperty); }
set { SetValue(artikelProperty, value); }
}
public static readonly DependencyProperty artikelProperty =
DependencyProperty.Register("artikel", typeof(Artikel), typeof(MainWindow), new UIPropertyMetadata(new Artikel()));
public MainWindow()
{
InitializeComponent();
test.DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
artikel.MyProperty += 1;
// What can I do at this point to update all bindings?
// What I know at this point is that control test or some of it's
// child controls bind to some property of artikel.
}
}
<Grid Name="test">
<TextBlock Text="{Binding Path=artikel.MyProperty}" />
</Grid>
This is, I tried to pack my object into a DependencyProperty and tried to call UpdateTarget on this, but didn't succeed.
What could I do to update the corresponding UI controls?
I hope I described my situation good enough.
Using INotifyPropertyChanged is a good alternative to DependencyProperties.
If you implement the interface you can raise the PropertyChanged event with null as parameter to notify the UI that all properties changed.
(I'm going to assume you can't add INotifyPropertyChanged to your business objects either, and that you don't want to add another "view of the data model" layer of wrapper objects a la MVVM.)
You can manually update bound properties from their data source by calling BindingExpression.UpdateTarget().
myTextBlock.GetBindingExpression(TextBlock.TextProperty).UpdateTarget();
To update all bindings on a control or window, you could use something like this:
using System.Windows.Media;
...
static void UpdateBindings(this DependencyObject obj)
{
for (var i=0; i<VisualTreeHelper.GetChildrenCount(obj); ++i)
{
var child = VisualTreeHelper.GetChild(obj, i);
if (child is TextBox)
{
var expression = (child as TextBox).GetBindingExpression(TextBox.TextProperty);
if (expression != null)
{
expression.UpdateTarget();
}
}
else if (...) { ... }
UpdateBindings(child);
}
}
If you're binding a diverse set of properties then rather than handling them individually as above, you could combine the above with this approach to enumerate all dependency properties on a control and then get any BindingExpression from each; but that relies on reflection which will not be particularly performant.
As a footnote, you can also use BindingExpression.UpdateSource() if you want to explicitly write back to the data source. Controls usually do this anyway when their value changes or when they lose focus, but you control this and do it by hand with {Binding Foo, UpdateSourceTrigger=Explicit}.
As I know exactly when I modify these objects, I would like to have a mechanism to update all UI controls when I do this.
You will find that the most straightforward and maintainable way to deal with this is to implement view model classes for each class you want to present in the UI. This is probably true if you can modify the underlying classes, and almost certainly true if you can't.
You don't need to be using dependency properties for this. Dependency properties are only necessary on the targets of binding, which is to say the controls in the UI. Your view model objects are the source; they need only implement INotifyPropertyChanged.
Yes, this means that you will need to build classes that contain a property for each property exposed in the UI, and that those classes will need to contain observable collections of child view models, and you'll have to instantiate and populate those classes and their collections at runtime.
This is generally not as big a deal as it sounds, and it may be even less of one in your case. The traditional way to build a view model that's bound to a data model is to build properties like this:
public string Foo
{
get { return _Model.Foo; }
set
{
if (value != _Model.Foo)
{
_Model.Foo = value;
OnPropertyChanged("Foo");
}
}
}
But if, as you've claimed, you know when the objects are being updated, and you just want to push the updates out to the UI, you can implement read-only properties, and when the underlying data model gets updated make the view model raise PropertyChanged with the PropertyName property of the event args set to null, which tells binding, "Every property on this object has changed; update all binding targets."