I have a RichTextBox in my MVVM program.
I would like to bind the RichTextBox.Selection property to my model.
To achieve this task, I've created a custom UserControl which contains a RichTextBox:
<UserControl x:Class="MyProject.Resources.Controls.CustomRichTextBox"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<RichTextBox x:Name="RichTextBox" SelectionChanged="RichTextBox_SelectionChanged"/>
</UserControl>
In my UserControl class:
// Selection property
public static readonly DependencyProperty TextSelectionProperty =
DependencyProperty.Register("TextSelection", typeof(TextSelection),
typeof(CustomRichTextBox));
[Browsable(true)]
[Category("TextSelection")]
[Description("TextSelection")]
[DefaultValue("null")]
public TextSelection TextSelection
{
get { return (TextSelection)GetValue(TextSelectionProperty); }
set { SetValue(TextSelectionProperty, value); }
}
The usage is:
<ResourcesControls:CustomRichTextBox TextSelection="{Binding ModelTextSelection}"/>
And I have this property on my model:
private TextSelection _TextSelection;
public TextSelection TextSelection
{
get { return _TextSelection; }
set { _TextSelection = value; }
}
I would like to get the RichTextBox.Selection property in my model, but TextSelection is always null.
I know I'm missing the binding between the RichTextBox.Selection property and his model but I don't know how to do it.
I think I'm missing something but I can't find what.
RichTextBox.Selection is not a DependancyProperty so you cannot bind to that.
But for your setup you just need to set the BindingMode as TwoWay on your UserControl (ssuming your model property name is ModelTextSelection)
<ResourcesControls:CustomRichTextBox TextSelection="{Binding ModelTextSelection, Mode=TwoWay}"/>
and in SelectionChanged method your need to update your TextSelection DependancyProperty with RichTextBox.Selection
private void RichTextBox_SelectionChanged(object sender, RoutedEventArgs e)
{
TextSelection = richTextBox.Selection;
}
Related
I'm attempting to understand the best way of wiring up a custom control to use Dependency Properties and a View Model. The Dependency Properties are implemented to expose properties that can be used in XAML to initialise the properties in the View Model. For example, in the code behind of a custom control I can define the following dependency property:
public static readonly DependencyProperty MyPropertyProperty = DependencyProperty.Register(
"MyProperty", typeof(string), typeof(MyControl), new PropertyMetadata(null));
public string MyProperty
{
get { return (string)GetValue(MyPropertyProperty); }
set { SetValue(MyPropertyProperty, value); }
}
where the View Model is defined as
public class MyControlViewModel : INotifyPropertyChanged
{
public MyControlViewModel()
{
_myProperty = "Default View Model string";
}
private string _myProperty;
public string MyProperty
{
get
{
return _myProperty;
}
set
{
_myProperty = value;
OnPropertyChanged("MyProperty");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
and the custom control binds to the View Model MyProperty as follows
<UserControl x:Class="MyProject.MyControlView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyProject">
<UserControl.DataContext>
<local:MyControlViewModel/>
</UserControl.DataContext>
<Grid>
<TextBlock Text="{Binding MyProperty}"/>
</Grid>
</UserControl>
Now since I have defined the Dependency Property MyProperty in the custom control's code behind, I want to be able to use this to initialise MyProperty in the ViewModel. So something like this
<Window x:Class="MyProject.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyProject"
Title="MainWindow" Height="350" Width="525">
<Grid>
<local:MyControlView MyProperty="This was set in XAML"/>
</Grid>
</Window>
Running the above will display the string "Default View Model string" that was set in the View Model's constructor. How do I hook up the Dependency Property value so that it correctly initialises the string in the View Model? i.e. it should display "This was set in XAML".
UPDATE
I can set a property changed callback in the code behind and set the value in the View Model, i.e.
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register("MyProperty", typeof(string), typeof(MyControlView), new PropertyMetadata("Default", OnMyPropertyChanged));
private static void OnMyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var view = d as MyControlView;
if (view != null)
{
var viewModel = view.DataContext as MyControlViewModel;
if (viewModel != null)
{
viewModel.MyProperty = e.NewValue as string;
}
}
}
Is this correct, or does it smell?
You have created dependency property that is nice. But, you have made tightly coupled by instiantiating your view model and using its property into code behind. Its wrong way.
You should inherit your class from "Control" and for binding value and create one DP like below in custom control class:
public static readonly DependencyProperty myValueProperty = DependencyProperty.Register(
"MyProperty", typeof(object), typeof(FieldControl), new FrameworkPropertyMetadata(null, myValueChanged));
Then,
private static void myValueChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var fc = dependencyObject as FieldControl;
if (fc != null)
{
fc.SetValue(BindingExpression1Key, fc.GetBindingExpression(ValueProperty));
fc.RaiseValueChangedEvent(dependencyPropertyChangedEventArgs);
}
}
Now, use it and bind your property like below:
<local:MyControlView MyProperty="{Binding MyProperty, Mode=TwoWay}"/>
You can update the ViewModel (INPC) property based on the View's Dependency Property (DP) using a Blend Behaviour. This solution avoids having to add a property changed callback in the code-behind:
<UserControl x:Class="MyProject.MyControlView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:ic="clr-namespace:Microsoft.Expression.Interactivity.Core;assembly=Microsoft.Expression.Interactions"
xmlns:local="clr-namespace:MyProject"
x:Name="MyControl">
<UserControl.DataContext>
<local:MyControlViewModel/>
</UserControl.DataContext>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<ic:ChangePropertyAction
TargetObject="{Binding}"
PropertyName="MyProperty"
Value="{Binding ElementName=MyControl, Path=MyProperty}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<Grid>
<TextBlock Text="{Binding MyProperty}"/>
</Grid>
You will have to add references to the System.Windows.Interactivity and Microsoft.Expression.Interactivity.Core assemblies to your projects in order to use Blend Behaviours (note the i and ic namespaces that I added).
In this case I hook into the UserControl Loaded event with an EventTrigger that calls a ChangePropertyAction. By specifying {Binding} for the TargetObject, I'm informing the Behaviour to use the View Model from the bound DataContext. The PropertyName refers to the property on the View Model. The Value refers to the DP on the View. I gave your UserControl a name so that I could easily reference it when querying the value of MyProperty.
The benefit of doing it this way means that you could swap out your View Model in the DataContext (as long as the new one also has a MyProperty property) without having to update the code-behind.
The task: implement the simplest Dependency Property ever, which can be used in xaml like that:
<uc:MyUserControl1 MyTextProperty="{Binding Text}"/>
I think that this answer is quite close. For better readability i copy all my code here (mostly from that answer above).
<UserControl x:Class="Test.UserControls.MyUserControl1"
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"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<!-- Text is being bound to outward representative property;
Note the DataContext of the UserControl -->
<TextBox Text="{Binding MyTextProperty}"/>
</Grid>
</UserControl>
and
public partial class MyUserControl1 : UserControl
{
// The dependency property which will be accessible on the UserControl
public static readonly DependencyProperty MyTextPropertyProperty =
DependencyProperty.Register("MyTextProperty", typeof(string), typeof(MyUserControl1), new UIPropertyMetadata(String.Empty));
public string MyTextProperty
{
get { return (string)GetValue(MyTextPropertyProperty); }
set { SetValue(MyTextPropertyProperty, value); }
}
public MyUserControl1()
{
InitializeComponent();
}
}
And this is my MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:uc="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<StackPanel Orientation="Vertical">
<uc:MyUserControl1 MyTextProperty="my text goes here"/>
<Button Click="ButtonBase_OnClick" Content="click"/>
</StackPanel>
</Window>
So far, everything works. However, i find this quite not usefull. What i'd need is
<uc:MyUserControl1 MyTextProperty="{Binding Text}"/>
and being able to change this by setting a DataContext (as you usually do in MVVM)
So i replace the line as above and add my code behind as follows:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
Text = "Initial Text";
DataContext = this;
}
private string _Text;
public string Text
{
get { return _Text; }
set
{
if (value != _Text)
{
_Text = value;
NotifyPropertyChanged("Text");
}
}
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
Text = "clicked";
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
Neither the "initial Text" nor the "clicked" is displayed... ever. So my question is how to implement a dept. property correctly to be used with
<uc:MyUserControl1 MyTextProperty="{Binding Text}"/>
The Text property is located on the DataContext of the MainWindow not of the UserControl.
So change this line <uc:MyUserControl1 MyTextProperty="{Binding Text}"/> into this:
<uc:MyUserControl1 MyTextProperty="{Binding Text, ElementName=MyMainWindow}"/>
Which will tell the Binding that you're talking about the Text element located in you MainWindow. Of course, since in this example I used ElementName, you're going to want to name your window MyMainWindow...
So add this to your MainWindow:
<Window Name="MyMainWindow" ..... />
If you rather not name your window, you can use the RelativeSource FindAncestor binding like this:
<wpfApplication6:MyUserControl1 MyTextProperty="{Binding Text, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}"/>
In both ways, you are asking to find the property named 'Text' in the DataContext of the window.
Very simple issue here. I have some checkboxes with their IsChecked bindings set to properties in my viewmodel.The binding mode is twoway. However, when they are checked, the viewmodel property isnt updated. I found a post about setting the clickmode of the checkbox and I have tried all the options:Hover, Press and Release. None of these fix the issue.
Is your property a nullable bool like the CheckBox.IsChecked?
Otherwise verify all that is needed for the MVVM pattern to work: your property is public with a getter and a setter, implementing INotifyPropertyChanged, etc.
Are the other properties binding properly? Your DataContext may be wrong...
Try this:
<Window x:Class="WpfTestApp.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>
<CheckBox Width="250" Height="30" IsChecked="{Binding Path=IsTrue, Mode=TwoWay}" />
<TextBlock Text="{Binding Path=IsTrue}" />
</StackPanel>
</Window>
Create ViewModel:
public class MainWindowViewModel :INotifyPropertyChanged
{
private bool _isTrue;
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChange(string propertyName)
{
if(PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public bool IsTrue
{
get { return _isTrue; }
set
{
_isTrue = value;
OnPropertyChange("IsTrue");
}
}
}
Bind to View Model in MainWindow.cs code behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
}
I'm trying to create a user control with dependency properties to bind to. Internally I have a ComboBox that is bound to these same properties, but the binding only works one way. The ComboBox fills from the ItemsSource, but SelectedItem doesn't get updated back to the viewmodel I'm binding to.
A simplified example:
This is the view model to bind with the user control:
public class PeopleViewModel : INotifyPropertyChanged
{
public PeopleViewModel()
{
People = new List<string>( new [] {"John", "Alfred","Dave"});
SelectedPerson = People.FirstOrDefault();
}
public event PropertyChangedEventHandler PropertyChanged;
private IEnumerable<string> _people;
public IEnumerable<string> People
{
get { return _people; }
set
{
_people = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("People"));
}
}
}
private string _selectedPerson;
public string SelectedPerson
{
get { return _selectedPerson; }
set
{
_selectedPerson = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("SelectedPerson"));
}
}
}
}
This is the User control:
<UserControl x:Class="PeopleControlTest.PeopleControl"
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"
mc:Ignorable="d" d:DesignHeight="56" d:DesignWidth="637">
<StackPanel >
<ComboBox Margin="11"
ItemsSource="{Binding BoundPeople, RelativeSource={RelativeSource AncestorType=UserControl}}"
SelectedItem="{Binding BoundSelectedPerson, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
</StackPanel>
with code behind
public partial class PeopleControl : UserControl
{
public PeopleControl()
{
InitializeComponent();
}
public static readonly DependencyProperty BoundPeopleProperty =
DependencyProperty.Register("BoundPeople", typeof(IEnumerable<string>), typeof(PeopleControl), new UIPropertyMetadata(null));
public static readonly DependencyProperty BoundSelectedPersonProperty =
DependencyProperty.Register("BoundSelectedPerson", typeof(string), typeof(PeopleControl), new UIPropertyMetadata(""));
public IEnumerable<string> BoundPeople
{
get { return (IEnumerable<string>)GetValue(BoundPeopleProperty); }
set { SetValue(BoundPeopleProperty, value); }
}
public string BoundSelectedPerson
{
get { return (string)GetValue(BoundSelectedPersonProperty); }
set { SetValue(BoundSelectedPersonProperty, value); }
}
}
And this is how I bind the user control in the main window (with the windows data context set to an instance of the viewmodel)
<Window x:Class="PeopleControlTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:PeopleControlTest"
Title="MainWindow" Height="350" Width="525">
<Grid>
<controls:PeopleControl
BoundPeople="{Binding People}"
BoundSelectedPerson="{Binding SelectedPerson}"/>
</Grid>
</Window>
The combobox in the user control fills with the names, but when I select a different name this doesn't get updated back to the view model. Any idea what I'm missing here?
Thanks!
Some properties bind two-way by default (Including SelectedItem) but your BoundSelectedPerson does not. You can set the Mode of the binding:
<controls:PeopleControl
BoundPeople="{Binding People}"
BoundSelectedPerson="{Binding SelectedPerson, Mode=TwoWay}"/>
Or you can make it TwoWay by default by setting a flag on the DependencyProperty:
public static readonly DependencyProperty BoundSelectedPersonProperty =
DependencyProperty.Register("BoundSelectedPerson", typeof(string), typeof(PeopleControl), new FrameworkPropertyMetadata("",FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
I have two UserControls (uc1 and uc2) loading into a third UserControl (shell). Shell has two properties, uc1 and uc2, of type UserControl1 and UserControl2, and each have a DependencyProperty registered to their own classes called IsDirty:
public static readonly DependencyProperty IsDirtyProperty = DependencyProperty.Register("IsDirty", typeof (bool), typeof (UserControl1));
public bool IsDirty
{
get { return (bool) GetValue(IsDirtyProperty); }
set { SetValue(IsDirtyProperty, value); }
}
(same code for UserControl2)
Shell has TextBlocks bound to the IsDirty properties:
<TextBlock Text="{Binding ElementName=shell, Path=Uc1.IsDirty}"/>
<TextBlock Text="{Binding ElementName=shell, Path=Uc2.IsDirty}"/>
When I change the values of IsDirty in uc1 and uc2, Shell never gets notified. What am I missing? UserControl is descendant of DependencyObject...
The same behavior occurs if I have regular properties notifying changes via INotifyPropertyChanged.
If I raise a routed event from uc1 and uc2, bubbling up to Shell, then I can catch the Dirty value and everything works, but I shouldn't have to do that, should I?
Thanks
Edit: The answer is to raise property changed event on the Uc1 and Uc2 properties or make them DPs.
I tried reproducing your problem using a simple setup, and it works fine for me. I'm not sure though if this setup is correct enough to replicate your situation. Anyway, I'm posting it just in case. It might be helpful:
XAML:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication2"
x:Name="shell"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<Button Click="Button_Click">Click</Button>
<TextBlock Text="{Binding ElementName=shell, Path=Uc1.IsDirty}"/>
</StackPanel>
</Window>
Code-Behind:
namespace WpfApplication2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private MyUserControl uc1 = new MyUserControl();
public MyUserControl Uc1
{
get { return this.uc1; }
}
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
this.uc1.IsDirty = !this.uc1.IsDirty;
}
}
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
}
public bool IsDirty
{
get { return (bool)GetValue(IsDirtyProperty); }
set { SetValue(IsDirtyProperty, value); }
}
// Using a DependencyProperty as the backing store for IsDirty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsDirtyProperty =
DependencyProperty.Register("IsDirty", typeof(bool), typeof(UserControl), new UIPropertyMetadata(false));
}
}
Karmicpuppet's answer works well. However it didn't solve my problem because Shell is also of type UserControl. For it to work I needed to raise the property changed on Uc1 and Uc2. When I declared them as DependencyProperties all worked as expected. Duh!