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.
Related
I've made a user control which contains a command, to be called in response to a certain event. This command is a dependency property. I want to use it in the main window like this:
<local:myUserControl Command="{Binding someCommand}"/>
The "myCommand" is the dependency property I created for this user control. And I bind it to a command of the view model of the main window ("someCommand").
The problem is that I am setting the datacontext of my usercontrol (I have a view model for it), and it seems to reset the "Command" to null… Here is the code-behind of my view model:
public partial class myUserControl : UserControl, ICommandSource
{
public myUserControl()
{
this.DataContext = new myViewModel();
InitializeComponent();
}
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(myUserControl), new PropertyMetadata(null));
public object CommandParameter
{
get { return (object)GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter", typeof(object), typeof(myUserControl), new PropertyMetadata(0));
public IInputElement CommandTarget
{
get { return (IInputElement)GetValue(CommandTargetProperty); }
set { SetValue(CommandTargetProperty, value); }
}
public static readonly DependencyProperty CommandTargetProperty =
DependencyProperty.Register("CommandTarget", typeof(IInputElement), typeof(myUserControl), new PropertyMetadata(null));
private void TextBlock_MouseUp(object sender, MouseButtonEventArgs e)
{
Command.Execute(this.CommandParameter);
}
}
The code of my user control could be the Following:
<UserControl x:Class="myApp.myUserControl"
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:myApp"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<TextBlock MouseUp="TextBlock_MouseUp">
</TextBlock>
</Grid>
</UserControl>
(I know that this element seems a bit silly (or useless), but I have simplified it to test what didn't worked and also in order to ask a rather simple question).
I have discovered that, if I comment the "this.DataContext = new myViewModel();" line, the binding to the command works perfectly. And when I uncomment this line and put a breakpoint in the "TextBlock_MouseUp", the "Command" property is equal to null...
Would there be a way to resolve this problem? I have some complicated code in my view model (so I'm quite forced to keep this line "this.DataContext = new myViewModel();"), and I am not sure I could find another solution than having a "Command" dependency property in my user control…
To be sure I give a maximum of informations, I have the following code in the view model of my main window:
public ICommand someCommand { get; set; }
//Constructor
public MainWindowViewModel()
{
this.someCommand = new RelayCommand((obj) => { return true; },
(obj) =>
{
//I put a breakpoint here
int dummy = 0;
});
}
(The RelayCommand class is a standard RelayCommand class, with a "Predicate" CanExecute and an "Action Execute).
I hope this question is not a duplicate… I have found several similar question, but they did not seem to answer mine...
I'm really sorry for this question which was in fact a bit silly. I hadn't understand very well what happens during a binding. I thought that this code line in the MainWindow…
<local:myUserControl Command="{Binding someCommand}"/>
…would have made an attempt to bind the UserControl's "Command" property to the "someCommand" of the datacontext of the MainWindow. In fact, as #elgonzo pointed out, the binding looks up in the UserControl's datacontext for the "someCommand" property (and not in the MainWindow's datacontext!!). Therefore, setting the UserControl's datacontext with this line…
this.DataContext = new myViewModel();
...was preventing the binding to be correctly done (since it looks for the "someCommand" property of the UserControl's datacontext, which is now "myViewModel", which does not contain "someCommand"...).
To fix this, I had to change the binding like this:
<local:myUserControl Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}},
Path=DataContext.someCommand}"/>
I've found this solution here: https://stackoverflow.com/a/1127964/11609068.
Maybe it is not the best way to do it (the "Path= DataContext. someCommand" make me think this, it doesn't seem very elegant), but it works. Another way to do it is to name the MainWindow (x:Name="someName"), so that the binding is a bit simpler:
<local:myUserControl Command="{Binding ElementName=someName, Path=DataContext.someCommand}"/>
Again, sorry and many thanks to #elgonzo.
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.
Using VS2015 I'm adding some custom functionality to a TextBlock for a small app and, since I can't derive from TextBlock itself (it's sealed), I'm deriving from UserControl.
In my xaml file, I have
<TextBlock x:Name="innerText"/>
As the only element within the usercontrol.
In my code-behind, I have the following used for accessing the text:
public string Label
{
get { return innerText.Text; }
set {
if (value != innerText.Text)
{
innerText.Text = value;
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs("Label"));
}
}
}
This works great when I'm running my app. On other pages, I am able to add instances of the control and set the "Label" property correctly. Unfortunately, the value of the "Label" property doesn't carry through to the inner textbox within the designer itself.
How can I get the value to update in the designer? While not strictly necessary (as I said, at run-time it works fine), it would make layout in the designer much easier for me.
Update:
I also tried using a DependencyProperty, with the same issue. Run-time works great, design-time shows nothing.
public string Label
{
get { return GetValue(LabelProperty).ToString(); ; }
set { SetValue(LabelProperty, value); }
}
public static readonly DependencyProperty LabelProperty = DependencyProperty.Register("Label", typeof(string), typeof(AutoSizingText), new PropertyMetadata(string.Empty));
And then, in the xaml, I set the DataContext for the entire control:
DataContext="{Binding RelativeSource={RelativeSource Self}}"
And tried to bind the Text value:
<TextBlock Text="{Binding Label}" />
I would recommend using a dependency property instead of relying on setting the innerText element's Text property. A dependency property will behave just like any other property on a control, including updating in design mode.
public string Label
{
get { return (string)GetValue(LabelProperty); }
set { SetValue(LabelProperty, value); }
}
// Using a DependencyProperty as the backing store for Label. This enables animation, styling, binding, etc...
public static readonly DependencyProperty LabelProperty =
DependencyProperty.Register("Label", typeof(string), typeof(MyClassName), new PropertyMetadata(string.Empty));
And your XAML will look like this:
<UserControl x:Name="usr" ...>
...
<TextBlock Text="{Binding Label, ElementName=usr}" ... />
...
</UserControl>
Pro tip: Type propdp, then Tab, Tab to quickly create a dependency property.
Here's an example usage:
<local:MyUserControl Label="Le toucan has arrived"/>
Note: You do not need to set the DataContext to Self when using a dependency property, this will generally screw things up as the UserControl should not set it's own DataContext, the parent control should.
Multiple sources on the net tells us that, in MVVM, communication/synchronization between views and viewmodels should happen through dependency properties. If I understand this correctly, a dependency property of the view should be bound to a property of the viewmodel using two-way binding. Now, similar questions have been asked before, but with no sufficient answer.
Before I start analyzing this rather complex problem, here's my question:
How do I synchronize a custom view's DependencyProperty with a property of the viewmodel?
In an ideal world, you would simply bind it as this:
<UserControl x:Class="MyModule.MyView" MyProperty="{Binding MyProperty}">
That does not work since MyProperty is not a member of UserControl. Doh! I have tried different approaches, but none proved successful.
One solution is to define a base-class, UserControlEx, with necessary dependency properties to get the above to work. However, this soon becomes extremely messy. Not good enough!
If you want to do it in XAML, you could try using styles to achieve that.
Here's an example:
<UserControl x:Class="MyModule.MyView"
xmlns:local="clr-namespace:MyModule">
<UserControl.Resources>
<Style TargetType="local:MyView">
<Setter Property="MyViewProperty" Value="{Binding MyViewModelProperty, Mode=TwoWay}"/>
</Style>
</UserControl.Resources>
<!-- content -->
</UserControl>
In your case both MyViewProperty and MyViewModelProperty would be named MyProperty but I used different names just to be clear about what is what.
I use Caliburn.Micro for separating the ViewModel from the View. Still, it might work the same way in MVVM. I guess MVVM sets the view's DataContext property to the instance of the ViewModel, either.
VIEW
// in the class of the view: MyView
public string ViewModelString // the property which stays in sync with VM's property
{
get { return (string)GetValue(ViewModelStringProperty); }
set
{
var oldValue = (string) GetValue(ViewModelStringProperty);
if (oldValue != value) SetValue(ViewModelStringProperty, value);
}
}
public static readonly DependencyProperty ViewModelStringProperty =
DependencyProperty.Register(
"ViewModelString",
typeof(string),
typeof(MyView),
new PropertyMetadata(OnStringValueChanged)
);
private static void OnStringValueChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
// do some custom stuff, if needed
// if not, just pass null instead of a delegate
}
public MyView()
{
InitializeComponent();
// This is the binding, which binds the property of the VM
// to your dep. property.
// My convention is give my property wrapper in the view the same
// name as the property in the VM has.
var nameOfPropertyInVm = "ViewModelString"
var binding = new Binding(nameOfPropertyInVm) { Mode = BindingMode.TwoWay };
this.SetBinding(SearchStringProperty, binding);
}
VM
// in the class of the ViewModel: MyViewModel
public string ViewModelStringProperty { get; set; }
Note, that this kind of implementation lacks completely of implementation of the INotifyPropertyChanged interface. You'd need to update this code properly.
Lets say you have defined your DependencyProperty "DepProp" in the View and want to use the exactly same value in your ViewModel (which implements INotifyPropertyChanged but not DependencyObject). You should be able to do the following in your XAML:
<UserControl x:Class="MyModule.MyView"
xmlns:local="clr-namespace:MyModule"
x:Name="Parent">
<Grid>
<Grid.DataContext>
<local:MyViewModel DepProp="{Binding ElementName=Parent, Path=DepProp}"/>
</Grid.DataContext>
...
</Grid>
</UserControl>
I have trouble to understand how dependency properties can be used between C# and xaml code.
This is a smal code example of my question
XAML code:
<Window x:Class="WpfChangeTextApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<Label Name="statusTextLabel" Content="{Binding StatusText}"></Label>
<Button Name="changeStatusTextButton" Click="changeStatusTextButton_Click">Change Text</Button>
</StackPanel>
C# code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public string StatusText
{
get { return (string)GetValue(StatusTextProperty); }
set { SetValue(StatusTextProperty, value); }
}
// Using a DependencyProperty as the backing store for StatusText. This enables animation, styling, binding, etc...
public static readonly DependencyProperty StatusTextProperty =
DependencyProperty.Register("StatusText", typeof(string), typeof(MainWindow));
private void changeStatusTextButton_Click(object sender, RoutedEventArgs e)
{
StatusText = "Button clicked";
}
}
So, my trouble is that Label statusTextLabel dose not get updated when I click on the button. My trouble is that I don't know in what part of the code that I'm doing something wrong, is it in the xaml or in the C#? In the xaml I might doing something wrong in the Binding? Or have I missed doing something in the C# code?
By default, binding paths are relative to the DataContext property of the current element. You have not set it to anything, so it can't resolve the binding. If you want the StatusText property on your window class, then there are two approaches. One is to use a binding with a RelativeSource of FindAncestor to find the Window in the tree and bind to its properties directly:
<Label Name="statusTextLabel" Content="{Binding StatusText,
RelativeSource={RelativeSource AncestorType=Window}}"></Label>
The other is to set the DataContext of the Window to itself, so it will be inherited by the label. For example, in your constructor:
public MainWindow()
{
this.DataContext = this;
InitializeComponent();
}
For most applications, you will actually want a separate class to represent the data, and you will set an instance of that class as the DataContext. You can also use ordinary CLR properties instead of dependency properties, although you will need to implement INotifyPropertyChanged if you want to UI to be informed when properties change. Dependency properties are more useful when you are writing a custom control and you want users to be able to set the properties using data binding.