I have an entity like this:
public class Person
{
public string Name { get; set; }
public Person()
{
Name = "Godspeed";
}
}
Then I have three textbox and a button in XAML:
<Window x:Class="WpfApplication19.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication19"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:Person />
</Window.DataContext>
<StackPanel>
<TextBox Text="{Binding Name,UpdateSourceTrigger=PropertyChanged}" />
<TextBox Text="{Binding Name,UpdateSourceTrigger=PropertyChanged}" />
<TextBox Text="{Binding Name,UpdateSourceTrigger=PropertyChanged}" />
<Button Click="Button_Click">Click</Button>
</StackPanel>
</Window>
The weird thing is that, the entity "Person" doesn't implement the INotifyPropertyChanged, but when one text box is changed, it modifies the source(Person object), but we didn't raised the property changed event but the rest two textboxes automatically changed.
When the button clicks, we update the source directly by code like:
((Person)DataContext).Name = "Godspeed";
It doesn't update. So what I think is that if the Person class implement the INotifyPropertyChanged, this behavior is normal, but now the class doesn't implement the interface, but it update the interface too. Please info me the reason if you have some clue. Thanks.
The reason is PropertyDescriptor, see the following thread,
the same question is being asked: How does the data binding system know when a property is changed?
Here is two of the answers
I think the magic lies in the binding system's use of
PropertyDescriptor (SetValue presumably raises a ValueChanged - the
PropertyDescriptor is likely shared, while the events are raised on a
per-object basis).
I'm not at Microsoft, but I can confirm it. If PropertyDescriptor is
used to update the value, as it will be, then relevant change
notifications are automatically propagated.
Edit
You can verify this by naming the Person DataContext object
<Window.DataContext>
<local:Person x:Name="person"/>
</Window.DataContext>
and add the following code to the MainWindow ctor
public MainWindow()
{
InitializeComponent();
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(person);
PropertyDescriptor nameProperty = properties[0];
nameProperty.AddValueChanged(person, OnPropertyChanged);
}
void OnPropertyChanged(object sender, EventArgs e)
{
MessageBox.Show("Name Changed");
}
Once you change the value on any of the three TextBoxes, you'll end up in the event handler OnPropertyChanged.
Well, as you said, you just have to implement INotifyPropertyChanged
The PropertyChanged event is used when you set a property from code and need to reflect this change to your UI (UI will cath the PropertyChanged event, and thanks to your UpdateSourceTrigger the UI will be updated). The other side (changing from UI) does not need any PropertyChanged, this is why you get this behavior
Just try it like that:
public class Person : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
/// <summary>
/// Property Changed Event
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Property Changed
/// </summary>
/// <param name="propertyName"></param>
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
private string name;
public string Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged("Name");
}
}
}
Using this code, when you set the Name, the PropertyChanged event will be fired and therefore update UI accordingly :)
It works not only with updatesourcetrigger=propertychanged, but with default (lost focus) value too. In addition to what #Meleak said, I want to point that it is good behaviour. Any changes made by ui are propagated to all binding targets. Binding engine wants to propagate this changes to all controls at once. If you make changes through code, and not implement INotifyPropertyChanged - changes made from code are not reflected at all. Again, for all controls with the same binding source. All controls works in the synchronized way with such implementation.
Related
I'm trying to use a 3rd party DLL object type for a data template in WPF treeview. For some reason, the "rdb:Element" object type is not found, however I can use all day in code, and even bind to this object type in other places, but cant seem to reference it in this way. Can someone help me understand why this certain datatype is not available to me in this way?
Please see the " " line in code below. I'm getting "Element does not exist in name space..." error.
<Window x:Class="Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:LotSpec.Revit"
xmlns:rdb="clr-namespace:Autodesk.Revit.DB;assembly=RevitAPI"
xmlns:m="clr-namespace:LotSpec.Revit.Models"
mc:Ignorable="d"
Title="Window1" Height="300" Width="300">
<Grid>
<TreeView ItemsSource="{Binding ObjectOptions}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type m:ObjectOpt}" ItemsSource="{Binding Elems}">
<TextBlock Text="{Binding Rule}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type rdb:Element}">
<TextBlock Text="{Binding Converter={StaticResource ElemToDisplayName}}" Margin="3"/>
</DataTemplate>
</TreeView.Resources>
</TreeView>
</Grid>
I am not exactly sure why it wouldn't allow you to bind to a Element type, but here's something that I do all the time and why I think it's not a great idea to bind to Element objects.
Normally when dealing with WPF you will need things like two-way bindings. Clicking a checkbox in your UI should update a property and vice versa. To make sure that works well, you need these classes to implement IPropertyChanged interface. The problem with binding to an object from external library is that you don't have control over what interfaces were implemented there. Sure, you can probably extend them. I would have been game with that. Anyways, the point is, that when doing WPF I like to have control, and always create wrapper classes for any Revit DB objects. For example, I need a UI element that is a checkbox, and uses a name to compare between objects. I can do that by creating a wrapper class around Element from Revit API. In this case, I am wrapping a DimensionType.
public class DimensionStyleWrapper : INotifyPropertyChanged
{
public string Name { get; set; }
public int Id { get; set; }
[JsonIgnore]
public object Self { get; set; }
public DimensionStyleWrapper()
{
}
public DimensionStyleWrapper(DimensionType t)
{
Name = t.Name;
Id = t.Id.IntegerValue;
Self = t;
}
/// <summary>
/// Method that validates that the current Dimension Style Wrapper is still a valid
/// object in context of provided Revit Document.
/// </summary>
/// <param name="doc">Revit Document.</param>
/// <returns>True if wrapper is valid or was validated, otherwise false.</returns>
public bool Validate(Document doc)
{
var e = doc.GetElement(new ElementId(Id));
if (e == null || e.Name != Name)
{
var dt = new FilteredElementCollector(doc)
.OfClass(typeof(DimensionType))
.WhereElementIsElementType()
.FirstOrDefault(x => x.Name == Name);
if (dt != null)
{
Id = dt.Id.IntegerValue;
Self = dt;
return true;
}
return false;
}
return true;
}
public override bool Equals(object obj)
{
var item = obj as ViewWrapper;
return item != null && Name.Equals(item.Name);
}
public override int GetHashCode()
{
return Name.GetHashCode();
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propname)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propname));
}
}
Notice the IPropertyChanged interface. That comes handy when I use that class in WPF ComboBox, or a CheckBox and need a two way binding. I have also created my own Equals override to use the object Name as a way to determine if two objects are the same. I also have a Validate routine in there, that makes sure that if given class was serialized from JSON, that it actually exists in the given model. For the kicker I have a property called Self that I use to store a reference to the original DimensionType that came from the DB. I don't always use that, but sometimes it comes handy. I can always get the latest object using the Id and doc.GetElement(). Anyways, the point I am making is that for WPF UI design, you want to have full control over the behavior of objects that you bind to, hence creating a custom wrapper class, has been a good workflow for me.
I'm in a situation where I need to empty a property in my model whenever someone changes a value in a combobox.
A side effect of this is, that whenever I change the value of the Combobox-Bound variable, the Combobox SelectionChanged event is triggered.
Is there anyway to know who is triggering this event. I'd like to know if it is triggered manually or by binding.
I'm looking in to the sender, but they look about the same.
Thank you,
This is a prime example of why WPF developers should use the MVVM design pattern. By relying on SelectionChanged events to control the flow of your code, you're losing your own control over what should happen and are resorting to having to know how an event is being triggered so that you can respond. That's reactive, not pro-active. As a developer, you should always know what can and will affect your code flow.
Rather, set your WPF page or control's DataContext to a viewmodel class that wraps your model and bind your combobox to the ViewModel properties, specifically SelectedItem in this case. This will simplify your coding immensely.
ViewModel
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<string> Names = new ObservableCollection<string>();
private string _selectedName;
private YourModel _model;
public ViewModel(YourModel model)
{
_model = model;
}
public string SelectedName
{
get { return _model.SelectedName; }
set
{
_model.SelectedName = value;
OnPropertyChanged();
}
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML
<ComboBox ItemsSource="{Binding Names}" SelectedItem="{Binding SelectedName}" />
Dropodownclosed is the event I best use here instead of selectionchanged. This will make sure that the event is triggered not by the data that is bound but by user interface interaction.
I am beginner to WPF and MVMM architecture. VI came across many links which explains about DataContext dependence property in WPF MVMM architecture,
i.e.
view.DataContext = new ViewModels.MainViewModel();
but they always made me confused. Although I have some basic idea about this DataContext like it is used to represent who's object we need in xaml file, but when blogs talks about tree structure inheritance of dataContext I gets confused. Can any one please help me with some very simple and clear example showing how this hierarchy of DataContext works?
Thanks in advanced.
The DataContext property specifies the default source for Data Binding. Consider the following example:
<TextBox Text="{Binding MyProperty}" />
What this Binding says: take the value of MyProperty from whatever object is inside the DataContext, convert it to a string and put it in the TextBox. So if we would set the DataContext of the TextBox to be an object of the following class:
public class Example {
int MyProperty { get { return 3; } }
}
Then, the Text of the TextBox would be set to 3.
What does it mean that the values Inherit? Consider a slightly more complex example:
<Window Name="MainWindow">
<StackPanel>
<TextBox Text="{Binding MyProperty}" />
...etc
If we would have 10 or more TextBox elements on our screen, it would be a lot of senseless work to assign the DataContext to each and every TextBox. To relieve this issue, the implementors of WPF decided that setting the DataContext on the MainWindow in our case would also apply it to everything inside that Window (all children, all nested elements) until the DataContext property is overwritten (i.e. we set the DataContext of the TextBox, then the TextBox and all its children would also receive this DataContext).
If you want to see this behavior in action, the same applies to the FontSize property, try setting the FontSize of your Window to 48 and see what happens to all the text in there!
The Datacontext property is the default source of all the binding of a View.
In MVVM, the Datacontext is used to link a ViewModel to a View.
As the Datacontext property is a dependence property, if you don't define it in a control, it will inherit from his father, etc.
Here is an exemple of MVVM implementation :
Parent of all ViewModel class (to implement INotifyPropertyChanged in all ViewModels) :
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Note : INotifyPropertyChanged allow your ViewModel to notify the View of a change (used for bindings).
Let's say I want a MainWindows (View) to be linked to a ViewModel :
public MainWindow()
{
InitializeComponent();
MainViewModel mainViewModel = new MainViewModel(this);
this.DataContext = mainViewModel;
}
With for ViewModel :
class MainViewModel : ViewModelBase
{
#region fields
private MainWindow mainWindow;
private string message = "Hello world !";
#endregion
#region properties
public MainWindow MainWindow
{
get
{
return this.mainWindow;
}
}
public string Message
{
get
{
return message;
}
set
{
this.message = value; OnPropertyChanged("Message");
}
}
// ...
#endregion
public MainViewModel(MainWindow mainWindow)
{
this.mainWindow = mainWindow;
}
}
So now if I want to bind a property of MainViewModel in my View (mainwindow), i just have to have a public property in my ViewModel and to create a binding in my XAML. I won't have to specify the source as the DataContext is the default source.
So MainWindow.xaml I can add :
<TextBox Text="{Binding Message}" />
While investigating on a seemingly unrelated issue, I've hit some unexpected binding behaviour. Having
class StringRecord : INotifyPropertyChanged
{
public string Key {get; set; } // real INPC implementation is omitted
public string Value { get; set; } // real INPC implementation is omitted
...
}
class Container
{
public ObservableKeyedCollection<string, StringRecord> Params { get; set; }
...
{
Now, when a TextBox is bound to one of the collection items in obvious way
<TextBox Text="{Binding Params[APN_HOST].Value}" />
the PropertyChanged event of the StringRecord's instance doesn't fire upon editing the text. But, rewriting it as
<TextBox DataContext="{Binding Params[APN_HOST]}" Text="{Binding Value}" />
makes the miracle, and the event begins to fire correctly.
Why?
In the second xaml sample the binding is observing a StringRecord which implements INotifyPropertyChanged and thus is notified of changes to the object.
In the first xaml sample it isn't clear what you are binding to.
If you set the DataContext to Container the binding is observing an object that doesn't implement the INotifyPropertyChanged interface. Because the path is still correct the Value property can still be read but you are missing out on the notifications.
The ObservableKeyedCollection class needs to fire PropertyChanged events as well as CollectionChanged events if you want the binding system to know about changes to properties accessed via string indexes.
To do this, make ObservableKeyedCollection implement INotifyPropertyChanged, and then add the following code to OnCollectionChanged:
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Item[]"));
}
See also this answer: PropertyChanged for indexer property.
I have created a few Custom Controls (NOT UserControls) with bind-able "ClearCommand" ICommand dependency properties. This property will do exactly what it sounds: it will clear all the values from the control (textboxes, etc). I also bind (some) of those same properties to the VM I describe below.
Now I'm stuck trying to trigger the ClearCommand in those controls in the following MVVM scenario:
I've added a few such controls into my View. The View also includes a "Save" button that binds to my ViewModel's SaveCommand DelegateCommand property.
What I need to happen is that, upon a successful save, the VM should trigger the ClearCommand on those controls found in the View.
UPDATE
I've added code examples below. I have a few controls that resemble the ExampleCustomControl. Also, just to note, I am open to restructuring some of this if it's completely off.
Example Control snippet:
public class ExampleCustomControl : Control {
public string SearchTextBox { get; set; }
public IEnumerable<CustomObject> ResultList { get; set; }
public ExampleCustomControl() {
ClearCommand = new DelegateCommand(Clear);
}
/// <summary>
/// Dependency Property for Datagrid ItemSource.
/// </summary>
public static DependencyProperty SelectedItemProperty = DependencyProperty.Register("SelectedItem",
typeof(CustomObject), typeof(ExampleCustomControl), new PropertyMetadata(default(CustomObject)));
public CustomObject SelectedItem {
get { return (CustomObject)GetValue(SelectedCustomObjectProperty); }
set { SetValue(SelectedCustomObjectProperty, value); }
}
public static DependencyProperty ClearCommandProperty = DependencyProperty.Register("ClearCommand", typeof(ICommand),
typeof(ExampleCustomControl), new PropertyMetadata(default(ICommand)));
/// <summary>
/// Dependency Property for resetting the control
/// </summary>
[Description("The command that clears the control"), Category("Common Properties")]
public ICommand ClearCommand {
get { return (ICommand)GetValue(ClearCommandProperty); }
set { SetValue(ClearCommandProperty, value); }
}
public void Clear(object o) {
SearchTextBox = string.Empty;
SelectedItem = null;
ResultList = null;
}
}
Example View snippet:
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<control:ExampleCustomControl Grid.Row="0"
SelectedItem="{Binding Selection, UpdateSourceTrigger=PropertyChanged}" />
<Button Grid.Row="1" x:Name="ResetButton" Command="{Binding SaveCommand}">
Save
</Button>
</Grid>
Example ViewModel:
public class TestViewModel : WorkspaceTask {
public TestViewModel() {
View = new TestView { Model = this };
SaveCommand = new DelegateCommand(Save);
}
private CustomObject _selection;
public CustomObject Selection {
get { return _selection; }
set {
_selection = value;
OnPropertyChanged("Selection");
}
}
public DelegateCommand SaveCommand { get; private set; }
private void Save(object o) {
// perform save
// clear controls
}
}
As others have said the VM shouldn't know about the view directly in MVVM so it doesn't make sense really that the VM triggers something on your custom control to clear everything.
I would have set the DataContext of the custom control to an object that has all the properties you want to clear, which are all each bound (two-way) to your textboxes etc. Then in the Save() method you can set a new object (which the custom control DataContext is bound to) and all the properties will be cleared for you (assuming you have implemented INotifyPropertyChanged on the object).
UPDATED:
As per my comment, see an example of the workaround for your current setup (untested btw):
public static DependencyProperty SelectedItemProperty = DependencyProperty.Register("SelectedItem",
typeof(CustomObject), typeof(ExampleCustomControl), new PropertyMetadata(default(CustomObject), OnSelectedItemChanged));
private static void OnSelectedItemChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
var cont = source as ExampleCustomControl;
//do all the clearing of txtboxes etc here....
cont.SearchTextBox = string.Empty;
}
But I would still try and move all this into the VM. i.e. have a clear command, like you do with the save command and bind the textbox text etc to a property in the VM and when the command is called it clears everything, which you can then easily call from the Save method in the VM too. But obviously I have no idea what you are trying to achieve in the long run or how selectedItem and the textboxes etc are related, so depends (as always) i guess.
It sounds like you are thinking about this the wrong way. In MVVM the ViewModel should never know anything about the custom controls (hence you are having a problem with this Clear functionality).
Your requirements are a bit vague, but have you considered:
1) If the properties are bound from the VM, can't the Control detect when these are changed?
2) If you really need to call Clear from the XAML layer and want to keep it pure MVVM, then consider something like the Expression Blend SDK's CallMethodAction.
As a followup to my comment. I suspect your command is targeting the View and clearing the TextBoxes directly. Instead, have your command target the ViewModel and clear the properties the View is bound to. Then you can have the command be a property on the ViewModel and call it whenever needed.