I have three projects in my solution:
My main WPF Application which contains a MainWindow + MainViewModel
UserControl Library with a UserControl (ConfigEditorView)
UIProcess class with the ViewModel for the UserControl (ConfigEditorViewModel)
In my MainWindow I want to use the UserControl with the ViewModel of UIProcess.
First I set the UserControl in my MainWindow:
<TabItem Header="Editor">
<Grid>
<cel:ConfigEditorView DataContext="{Binding ConfEditModel, NotifyOnSourceUpdated=True, NotifyOnTargetUpdated=True, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</TabItem>
I don't know which of these properties I need here, so I put all together but it still doesn't work.
Then I've set this in my MainViewModel:
public ConfigEditorViewModel ConfEditModel { get; set; }
With simple method that is bound to a Button:
private void doSomething()
{
ConfEditModel = new ConfigEditorViewModel("Hello World");
}
My ConfigEditorViewModel looks basically like this:
public class ConfigEditorViewModel : ViewModelBase
{
private string _Description;
public string Description
{
get
{
return _Description;
}
set
{
_Description = value;
base.RaisePropertyChanged();
}
}
public ConfigEditorViewModel(string t)
{
Description = t;
}
}
The description is bound to a TextBox in my UserControl.
<TextBox Grid.Row="1" Grid.Column="1" Margin="0,0,0,10" Text="{Binding Description}"/>
When I start the application and click the Button the TextBox should contain "Hello World" but it's empty.
What I've done wrong?
i gave you a general answer:
within a "real(a usercontrol you wanna use with different viewmodels with different property names)" usercontrol you bind just to your own DependencyProperties and you do that with ElementName or RelativeSource binding and you should never set the DataContext within a UserControl.
<UserControl x:Name="myRealUC" x:class="MyUserControl">
<TextBox Text="{Binding ElementName=myRealUC, Path=MyOwnDPIDeclaredInMyUc, Path=TwoWay}"/>
<UserControl>
if you do that you can easily use this Usercontrol in any view like:
<myControls:MyUserControl MyOwnDPIDeclaredInMyUc="{Binding MyPropertyInMyViewmodel}"/>
and for completeness: the Dependency Property
public readonly static DependencyProperty MyOwnDPIDeclaredInMyUcProperty = DependencyProperty.Register(
"MyOwnDPIDeclaredInMyUc", typeof(string), typeof(MyUserControl), new PropertyMetadata(""));
public bool MyOwnDPIDeclaredInMyUc
{
get { return (string)GetValue(MyOwnDPIDeclaredInMyUcProperty); }
set { SetValue(MyOwnDPIDeclaredInMyUcProperty, value); }
}
Your view models (and, optionally, models) need to implement INotifyPropertyChanged.
Binding's aren't magic. There is no inbuilt mechanism that allows for code to be notified when a plain old property's value changes. You'd have to poll it in order to check to see if a change happened, which would be very bad, performance-wise.
So bindings will look at the objects they are bound against and see if they implement INotifyPropertyChanged and, if so, will subscribe to the PropertyChanged event. That way, when you change a property and fire the event, the binding is notified and updates the UI.
Be warned, you must implement the interface and use it correctly. This example says it's for 2010, but it works fine.
Related
This question already has answers here:
Issue with DependencyProperty binding
(3 answers)
Closed 4 years ago.
I would like to be able to bind complex model (many properties) to UserControl through DependencyProperty, and if model would be edited in UserControl I would like to see this edited information inside my binded model.
Example application: Model, UserControl (xaml + cs), MainWindow (xaml + cs). I have no ViewModel to simplify idea.
Model:
public class MyModel : INotifyPropertyChanged
{
private string _surname;
private string _name;
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged();
}
}
public string Surname
{
get => _surname;
set
{
_surname = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
MyModelEditor.xaml (inside Grid):
<DockPanel>
<TextBox Text="{Binding MyModel.Name}"/>
<TextBox Text="{Binding MyModel.Surname}"/>
</DockPanel>
Also contains this line in UserControl root element:
DataContext="{Binding RelativeSource={RelativeSource Self}}"
MyModelEditor.xaml.cs:
public partial class MyModelEditor : UserControl
{
public MyModel MyModel
{
get => (MyModel)GetValue(MyModelProperty);
set => SetValue(MyModelProperty, value);
}
public static readonly DependencyProperty MyModelProperty =
DependencyProperty.Register("MyModel", typeof(MyModel), typeof(MyModelEditor), new FrameworkPropertyMetadata(null));
public MyModelEditor()
{
InitializeComponent();
}
}
MainWindow.xaml (inside Grid):
<DockPanel>
<Button DockPanel.Dock="Bottom" Content="Press Me!" Click="ButtonBase_OnClick"/>
<controls:MyModelEditor MyModel="{Binding MyModel}"/>
</DockPanel>
MainWindow.xaml.cs:
public partial class MainWindow : Window, INotifyPropertyChanged
{
private MyModel _myModel;
public MyModel MyModel
{
get => _myModel;
set
{
_myModel = value;
OnPropertyChanged();
}
}
public MainWindow()
{
InitializeComponent();
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
MessageBox.Show(MyModel?.Name);
}
}
My test scenario: type text in textbox, press button.
Current behavior: Message after pressing button is empty.
Expected behavior: Message after pressing button is same like in textbox.
I wold not like to bind to all properties separately, because in future I will have much more then two properties.
Why current approach does not work?
How can I achieve my goal?
You are apparently not using the UserControl instance as Binding source in your UserControl's XAML. One way to do this would be to set the Binding's RelativeSource:
<TextBox Text="{Binding MyModel.Name,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>
However, you don't need a new dependency property at all for this purpose. Just bind the UserControl's DataContext to a MyModel instance, like
<controls:MyModelEditor DataContext="{Binding MyModel}"/>
The Bindings in the UserControl's XAML would automatically work with the MyModel object, like this:
<DockPanel>
<TextBox Text="{Binding Name}"/>
<TextBox Text="{Binding Surname}"/>
</DockPanel>
For both of your TextBox controls, you should define their Binding with a TwoWay mode (ms docs on binding modes). Which, basically, would assure that the data flow is working in both direction (i.e. from the view model into the view and the other way around):
<DockPanel>
<TextBox Text="{Binding MyModel.Name, Mode=TwoWay}"/>
<TextBox Text="{Binding MyModel.Surname, Mode=TwoWay}"/>
</DockPanel>
As a good practice, you should always explicitly define what is the mode of the the Binding (NOTE: by default it's OneWay TwoWay - how to know which is the default?).
Another tip would be to go ahead and use MvvmHelpers nuget (github project), which could spare you the time of implementing INotifyPropertyChanged. Besides, you shouldn't re-invent the wheel
EDIT: Fixes are in your GitHub repo
Two things to note here
You have not instantiated your ViewModel (i.e. MyModel), so it was always null
You don't need to create DependencyPropery every time you want to pass some information to your UserControl. You could simply bind the DataContext itself
I'm working on a "simple" case. I like to create a new custom control which implements a DependencyProperty. In the next step I like to create a binding for updating the properties in both directions. I've builded a simple sample for this case, but the binding doesn't seem to work. I've found a way for updating the DPControl's property by using the FrameworkPropertyMetadata, but I don't know whether it's also a good idea to use the OnPropertyChanged event.
HERE is my sample project:
My control contains simply a Label
<UserControl x:Class="WPF_MVVM_ListBoxMultiSelection.DPControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WPF_MVVM_ListBoxMultiSelection"
mc:Ignorable="d" Height="84.062" Width="159.641">
<Grid Margin="0,0,229,268">
<Label Content="TEST" x:Name="label" Margin="0,0,-221,-102"/>
</Grid>
</UserControl>
and implement a custom dependency property. Currently, I have also implemented the PropertyChanged method for the FramePropertyMetadata and set in this method the label's content, but I like to get it work in both directions.
public partial class DPControl : UserControl
{
public DPControl()
{
InitializeComponent();
}
public string MyCustomLabelContent
{
get { return (string)GetValue(MyCustomLabelContentProperty);}
set
{
SetValue(MyCustomLabelContentProperty, value);
}
}
private static void OnMyCustomLabelContentPropertyChanged(DependencyObject source,
DependencyPropertyChangedEventArgs e)
{
DPControl control = (DPControl)source;
control.label.Content = e.NewValue;
}
public static readonly DependencyProperty MyCustomLabelContentProperty = DependencyProperty.Register(
"MyCustomLabelContent",
typeof(string),
typeof(DPControl),
new FrameworkPropertyMetadata(null,
OnMyCustomLabelContentPropertyChanged
)
);
I use this control simply in a Window by:
<local:DPControl MyCustomLabelContent="{Binding MyLabelContent, Mode=TwoWay}" Margin="72,201,286,34"/>
MyLabelContent is a property in the ViewModel, which has implemented also the INotifyPropertyChanged interface.
public class ViewModel_MainWindow:NotifyPropertyChanged
{
private string _myLabelContent;
public string MyLabelContent
{
get { return _myLabelContent; }
set { _myLabelContent = value;
RaisePropertyChanged();
}
}...
So how can I get it work: Using the binding feature with my new control on custom properties.
In your UserControl:
<Label
Content="{Binding MyCustomLabelContent, RelativeSource={RelativeSource AncestorType=UserControl}}"
x:Name="label" Margin="0,0,-221,-102"/>
And get rid of that property-changed callback. All you need is the Binding.
I like to get it work in both directions
To make the dependency property two-way by default:
public static readonly DependencyProperty MyCustomLabelContentProperty =
DependencyProperty.Register(
"MyCustomLabelContent",
typeof(string),
typeof(DPControl),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)
);
I omitted the unnecessary property change handler.
It can't usefully be two-way now, because Label.Content can't generate its own value. If you want your UserControl to set the value in its codebehind, that's easy:
MyCustomLabelContent = "Some arbitrary value";
If you did the binding like I showed you, that will update the Label in the UserControl XAML as well as the viewmodel property bound to the UserControl's dependency property.
If you want the XAML to set it, you'll need to
Lastly, this:
Margin="0,0,-221,-102"
Is not a good way to do layout. WPF layout with Grid, StackPanel, etc. is much easier and more robust.
I am trying (and failing) to do data binding on a dependency property in xaml. It works just fine when I use code behind, but not in xaml.
The user control is simply a TextBlock that bind to the dependency property:
<UserControl x:Class="WpfTest.MyControl" [...]>
<TextBlock Text="{Binding Test}" />
</UserControl>
And the dependency property is a simple string:
public static readonly DependencyProperty TestProperty
= DependencyProperty.Register("Test", typeof(string), typeof(MyControl), new PropertyMetadata("DEFAULT"));
public string Test
{
get { return (string)GetValue(TestProperty); }
set { SetValue(TestProperty, value); }
}
I have a regular property with the usual implementation of INotifyPropertyChanged in the main window.
private string _myText = "default";
public string MyText
{
get { return _myText; }
set { _myText = value; NotifyPropertyChanged(); }
}
So far so good. If I bind this property to a TextBlock on the main window everything works just fine. The text update properly if the MyText changes and all is well in the world.
<TextBlock Text="{Binding MyText}" />
However, if I do the same thing on my user control, nothing happens.
<local:MyControl x:Name="TheControl" Test="{Binding MyText}" />
And now the fun part is that if I do the very same binding in code behind it works!
TheControl.SetBinding(MyControl.TestProperty, new Binding
{
Source = DataContext,
Path = new PropertyPath("MyText"),
Mode = BindingMode.TwoWay
});
Why is it not working in xaml?
The dependency property declaration must look like this:
public static readonly DependencyProperty TestProperty =
DependencyProperty.Register(
nameof(Test),
typeof(string),
typeof(MyControl),
new PropertyMetadata("DEFAULT"));
public string Test
{
get { return (string)GetValue(TestProperty); }
set { SetValue(TestProperty, value); }
}
The binding in the UserControl's XAML must set the control instance as the source object, e.g. by setting the Bindings's RelativeSource property:
<UserControl x:Class="WpfTest.MyControl" ...>
<TextBlock Text="{Binding Test,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>
</UserControl>
Also very important, never set the DataContext of a UserControl in its constructor. I'm sure there is something like
DataContext = this;
Remove it, as it effectively prevents inheriting a DataContext from the UserConrol's parent.
By setting Source = DataContext in the Binding in code behind you are explicitly setting a binding source, while in
<local:MyControl Test="{Binding MyText}" />
the binding source implicitly is the current DataContext. However, that DataContext has been set by the assignment in the UserControl's constructor to the UserControl itself, and is not the inherited DataContext (i.e. the view model instance) from the window.
I'm trying to create a WPF UserControl which contains 2 buttons. I use this UserControl in a Window and apply a Window.Resource value to set the background of one button inside the user control.
Currently I have:
window.xaml
<Window.Resources>
<SolidColorBrush Color="Brown" x:Key="theBG"></SolidColorBrush>
</Window.Resources>
<theControl:TheControl
x:Name="TheControl"
buttonBG="{Binding Source={StaticResource theBG}}" />
usercontrol.xaml.cs
public SolidColorBrush buttonBG
{
get { return base.GetValue(buttonBGProperty) as SolidColorBrush; }
set { base.SetValue(buttonBGProperty, value); }
}
public static readonly DependencyProperty buttonBGProperty = DependencyProperty.Register("buttonBG", typeof(SolidColorBrush), typeof(DataPanel), null);
usercontrol.xaml
<Button ... Background="{Binding buttonBG}">
I was expecting this to work but the background is not the one I set in the window resource.
What am I doing wrong?
Background="{Binding buttonBG}"
Implies either that you changed the DataContext of the UserControl, which you should never do. Or that the binding is just wrong.
Use
Background="{Binding buttonBG, ElementName=control}"
Naming your UserControl root element control. RelativeSource works as well.
Try placing it in a separate model or even a viewmodel that has INotifyPropertyChanged. When you add view code in the cs for an xaml file, you need to bind with relativesource self and its hacky and goes against MVVM. I would create a seperate ViewModel with a Brush that has NotifyPropertyChanged baked into it. This will tell the UI to change everything its bound to on value change.
In the Window, bind your viewmodel to datacontext. In the viewmodel you can put:
private Brush _bgColor;
public Brush BgColor
{
get{return _bgColor;
}
set
{
_bgColor = value;
OnPropertyChanged("BgColor");
}
Create an ICommand, and bind your button to it like this in the viewmodel:
ICommand ChangeBgColor {get;set;
And in the XAML for the Button:
Command="{Binding Path=DataContext.ChangeBgColor,RelativeSource={RelativeSorce Mode=FindAncestor,AncestorType={x:Type UserControl}}"
This will fire the ICommand, bound to the viewmodel that is the datacontex of the window that you are working with.
And in that code for the ICommand change out your colors, you could do it like this:
private void OnChangeBgColor(object param){
var bc = new BrushConverter();
BgColor = (Brush)bc.ConvertFrom("#fff");
}
With the MVVM pattern, you want to get away from putting unnecessary code in the xaml.cs files and start putting them into viewmodels and models.
I'm making a Ribbon control for a WYSIWYG HTML editor. The ribbon has the typical Bold, Italic, Underline, FontFamily, etc. controls that you'd expect to see. I'll focus on the Bold functionality for this example.
I want the Ribbon to be reuseable, so I've added a Dependency Property (DP) and associated property wrapper to the control's code behind (standard boilerplate stuff):
public partial class EditorRibbon: UserControl
{
public static readonly DependencyProperty IsBoldProperty =
DependencyProperty.Register(
"IsBold",
typeof (bool),
typeof (EditorRibbon),
new PropertyMetadata(default(bool)));
public bool IsBold
{
get { return (bool) GetValue(IsBoldProperty); }
set { SetValue(IsBoldProperty, value); }
}
}
... and in the XAML I have my RibbonToggleButton, and I've bound the IsChecked property to the dependency property:
<UserControl x:Class="My.EditorRibbon">
<r:RibbonToggleButton Command="ToggleBold"
ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=Command.Text}"
SmallImageSource="{StaticResource ToggleBoldIcon}"
IsChecked="{Binding IsBold}" />
</UserControl>
In my Editor window, I've bound the IsBold property of the EditorRibbon to a conventional property on the window's ViewModel:
<Window x:class="My.MainWindow>
<My.EditorRibbon IsBold="{Binding SelectionIsBold}"/>
</Window>
Here is the SelectionIsBold property:
public bool SelectionIsBold
{
get { return _selection.IsBold(); }
}
... and I raise the NotifyPropertyChanged() event (in the MainWindow's ViewModel) whenever the selection in the RichTextBox changes:
public class MainWindowViewModel : BaseViewModel
{
public MainWindowViewModel(MainWindow window)
{
rtb.SelectionChanged += rtb_OnSelectionChanged;
}
private void rtb_OnSelectionChanged(object sender, RoutedEventArgs routedEventArgs)
{
NotifyPropertyChanged(()=>SelectionIsBold);
}
}
To my mind, this should be enough to change the IsChecked state of the RibbonToggleButton whenever the selection changes... but it doesn't. Despite changing the selection, and despite the NotifyPropertyChanged() firing as expected, a breakpoint on the SelectionIsBold property (yes, I've deselected VS's "Step Over Property" setting) is never hit. Somewhere, the request to refresh the value isn't propagating correctly.
Do I need to trigger NotifyPropertyChanged() on the IsBold property after the value is set in the setter?
Change the IsBold binding to the following
<UserControl x:Class="My.EditorRibbon" x:Name="EditorRibbonInstance">
<r:RibbonToggleButton Command="ToggleBold"
ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=Command.Text}"
SmallImageSource="{StaticResource ToggleBoldIcon}"
IsChecked="{Binding IsBold, ElementName=EditorRibbonInstance, Mode=TwoWay}" />
</UserControl>
With that you are sure that the binding is going to the property of the control and not to the datacontext of the control
You have to fire notifypropertychanged in ViewModel. Try somethings like this in ViewModel:
protected void FirePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
->> FirePropertyChanged("SelectionIsBold")
The reason is: now, your data context is ViewModel, all of binding to ViewModel must be triggered by ViewModel's properties
first of all, I never saw the injection of the Window to the ViewModel before... are you using some Kind of DI for the injection?
I think it is not a good idea to use the selection changed Event on viewmodel... This is not mvvm from my Point of view...
Are you updating the _selection somewhere? Might be that you always checking the same selection?!
You are not properly binding the command property of your button.
Should reflect something like this:
Command="{Binding ToggleBold}"