How to set a Dependency Property Value? - c#

Please reference code below for context.
On start up, the Text of the 2 TextBoxes will be "This is the Original Value".
When the TestBox's button ("Test Button") is clicked:
the text of the TestBox's TextBox will change to "Set By Test Button"
the other TextBox's value will NOT change.
When the Window's button is clicked, the text of BOTH TextBoxes should change to "Set By Window". However, only the plain TextBox gets updated, the TestBox does not. <-- THIS IS THE BUG!
It seems that the way i'm (re)setting the Test property from within the TestBox obliterates the binding.
What is the proper way of changing a Dependency Property from within the user control itself without breaking bindings?
Example code:
I've got a UserControl, TestBox that looks like this:
TestBox.xaml:
<UserControl x:Class="Company.UserControls.TestBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="TextBoxControl">
<StackPanel>
<TextBox MinWidth="100" Name="TestTextBox"
Text="{Binding Path=Test, ElementName=TextBoxControl, Mode=TwoWay}"
/>
<Button MinWidth="100" Content="Test Button"
Click="ButtonBase_OnClick" />
</StackPanel>
</UserControl>
TestBox.xaml.cs:
using System.Windows;
namespace Company.UserControls
{
public partial class TestBox
{
public const string TestString = "Set By Test Button";
public TestBox()
{
InitializeComponent();
}
public static readonly DependencyProperty TestProperty =
DependencyProperty.Register(
"Test",
typeof(string), typeof(TestBox),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.AffectsRender));
public string Test
{
get { return (string)GetValue(TestProperty); }
set { SetValue(TestProperty, value); }
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
/****** THIS OBLITERATES THE BINDING ******/
Test = TestString;
/****** THIS OBLITERATES THE BINDING ******/
}
}
}
And a Window that uses the control like this:
MainWindow.xaml:
<Window x:Class="Company.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:u="clr-namespace:Company.UserControls"
Title="MainWindow">
<StackPanel x:Name="MyStackPanel">
<TextBox Text="{Binding Path=MyTestValue, Mode=OneWay}"/>
<u:TestBox x:Name="MyTestBox"
Test="{Binding Path=MyTestValue, Mode=OneWay}"/>
<Button Content="Click" Click="ButtonBase_OnClick" />
</StackPanel>
</Window>
MainWindow.xaml.cs:
using System.Windows;
namespace Company
{
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
MyStackPanel.DataContext = new MyThing
{
MyTestValue = "This is the Original Value"
};
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
MyStackPanel.DataContext = new MyThing
{
MyTestValue = "Set by Window"
};
}
}
public class MyThing
{
public string MyTestValue { get; set; }
}
}

The problem is that you are asking the binding system to get out of sync. The whole system is designed to keep all bound elements in sync. The only cases under which you can set a value on a dependency property without destroying the underlying binding are when the binding mode is set to "TwoWay" or "OneWayToSource". Under these conditions the value is transferred back to the source and consequently, the system is kept in sync. However, in your case a two way binding will cause both buttons to change both textboxes.
You will need to use two dependency properties TestBox. The first dependency property will be bound to the internal text box, and the second will be bound to in the parent window. Then you will need to add a property change handler to the second dependency property (which is done in the FrameworkPropertyMetadata). In this handler, simply set the value on the first dependency property.
Since you are using a UserControl with a code behind anyways, a simpler solution is to only have the second dependency property mentioned above and to directly set the value (from you event handler and the property change handler) onto the textbox via its x:Name.
Let me know if you need any more clarification.

Related

Passing SelectedItem of a TreeView UserControl to the calling Window

I've created a dialog (Window) in which I'm using a UserControl with a TreeView. Now I need the SelectedItem of the TreeView (the Getter) in my dialog/window.
I have tried it with a DependencyProperty, but if I set a BreakPoint to the SelectedItem property of the dialog, it doesn't trigger.
My Window:
<itemViewer:ItemViewerControl DataContext="{Binding ListOfWorldItems}"
SelectedItem="{Binding SelectedWorldItem, Mode=TwoWay}"/>/>
with CodeBehind:
public WorldItem SelectedWorldItem
{
get { return selectedWorldItem; }
set
{
selectedWorldItem = value;
NotifyPropertyChanged("SelectedWorldItem");
}
}
My UserControl
<itemViewer:ItemViewerControl DataContext="{Binding ListOfWorldItems}" />
with CodeBehind:
<UserControl ... >
<UserControl.Resources>
...
</UserControl.Resources>
<Grid>
<TreeView x:Name="WorldItemsTreeView"
SelectedItemChanged="TreeView_SelectedItemChanged"
ItemsSource="{Binding}" />
</Grid>
</UserControl>
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(WorldItem), typeof(ItemViewerControl), new UIPropertyMetadata(null));
public ItemViewerControl()
{
InitializeComponent();
DataContext = this;
}
public WorldItem SelectedItem
{
get { return (WorldItem)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
SelectedItem = (WorldItem)WorldItemsTreeView.SelectedItem;
}
The issue that you are running into is that you are setting the DataContext on your UserControl and then attempting to declare a binding. By default the binding will source its value from the DataContext of the element which contains the binding. In this case that is ListOfWorldItems, not the dialog. So the binding of the SelectedItem property on the UserControl actually fails (you can see this in the output window when debugging the application).
One way to resolve this is to explicitly set the source for the binding, instead of relying on the default behavior. If you change the line in your dialog to the following...
<itemViewer:ItemViewerControl DataContext="{Binding ListOfWorldItems}"
SelectedItem="{Binding SelectedWorldItem, Mode=TwoWay, RelativeSource={RelativeSource AncestorType=Window}"/>
It should now look to your dialog as the source for the binding and correctly establish binding between your UserControl and the dialog. Be careful that any other bindings you establish between your UserControl and the dialog also explicitly establish the source, otherwise they will run into the same problem you encountered here.
It doesn't look like it is contributing to the issue but as an additional note you are setting the DataContext for your UserControl twice. Once in the constructor for the UserControl you are self-referencing and then it is overwritten when setting the DataContext in the dialog. In this case it doesn't look like it is causing a problem (aside from some minor inefficiency), but it could have unexpected side-effects if you ever changed how the DataContext for the UserControl is being set in the dialog.

UserControl DataContext Binding

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.

WPF Binding ViewModel property to Attached Property

My goal is to create add Text property to RichTextBox. I created Attached Property and set binding to ViewModel's property. Unfortunately changing text in RichTextBox doesn't update underlying property.
Here is my code:
View.cs:
public partial class KnuthMorrisPrattView : UserControl
{
public KnuthMorrisPrattView()
{
InitializeComponent();
}
public static string GetText(DependencyObject obj)
{
return (string)obj.GetValue(TextProperty);
}
public static void SetText(DependencyObject obj, string value)
{
obj.SetValue(TextProperty, value);
}
public static readonly DependencyProperty TextProperty = DependencyProperty.RegisterAttached
(
"Text",
typeof(string),
typeof(KnuthMorrisPrattView),
new FrameworkPropertyMetadata()
{
BindsTwoWayByDefault = true,
PropertyChangedCallback = PropertyChangedCallback,
CoerceValueCallback = CoerceValueCallback,
DefaultUpdateSourceTrigger = UpdateSourceTrigger.LostFocus
}
);
private static object CoerceValueCallback(DependencyObject dependencyObject, object baseValue)
{
throw new NotImplementedException();
}
private static void PropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
throw new NotImplementedException();
}
}
View.xaml:
<UserControl x:Class="Launcher.Views.KnuthMorrisPrattView"
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:views="clr-namespace:Launcher.Views"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="500"
DataContext="{Binding KnuthMorrisPrattViewModel, Source={StaticResource MainViewModel}}">
<Grid Margin="15">
<Grid.RowDefinitions>
<RowDefinition Height="7*"/>
<RowDefinition Height="3*"/>
</Grid.RowDefinitions>
<DockPanel Grid.Row="0">
<Label Content="Text" DockPanel.Dock="Top"></Label>
<RichTextBox x:Name="TextBox" views:KnuthMorrisPrattView.Text="{Binding TextToSearchArg}"/>
</DockPanel>
<DockPanel Grid.Row="1">
<Label Content="Pattern" DockPanel.Dock="Top"></Label>
<TextBox Text="{Binding PatternArg}"/>
</DockPanel>
</Grid>
ViewModel.cs:
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using GalaSoft.MvvmLight.CommandWpf;
using Launcher.Runners.KnuthMorrisPratt;
namespace Launcher.ViewModels
{
public class KnuthMorrisPrattViewModel : ViewModelBase
{
private string _textToSearchArg;
private string _patternArg;
public string TextToSearchArg
{
get { return _textToSearchArg; }
set
{
_textToSearchArg = value;
RaisePropertyChanged();
}
}
public string PatternArg
{
get { return _patternArg; }
set
{
_patternArg = value;
RaisePropertyChanged();
}
}
public KnuthMorrisPrattViewModel()
{
}
}
}
I know that Callback throws and exception but my goal here is to just see under the debugger that this callback is invoked. Then I add correct implementation.
EDIT:
I think I missed important note about my issue. When I update TextToSearchArg property from code everything works correctly. The only problem is that when I set some text in RichTextBox underlying property is not updated.
What am I missing? Thanks a lot in advance.
Nothing in your code shows that the Attached property is bound to the RichTextBox events, hence it won't ever be called if the content/text in RichTextBox changes.
You'd need to subscribe to the RichTextBox.TextChanged event.
public partial class KnuthMorrisPrattView : UserControl
{
public KnuthMorrisPrattView()
{
InitializeComponent();
this.TextBox.TextChanged += OnTextChanged;
}
...
public void OnTextChanged(object sender, TextChangedEventArgs e)
{
// Get the text from the event and set your Text Property
string text = ...;
SetText(this, text);
}
}
Edit:
In case you want to listen to another control's Dependency/Attached Property changes, use
DependencyPropertyDescriptor.FromProperty(ControlClassName.DesiredPropertyProperty, typeof(ControlClassName)).AddValueChanged(dependencyObject, OnDesiredPropertyChanged);
Where...
ControlClassName is the class containing the Dependency Property (i.e. RichTextBox in your case or the class where the Dependency/Attached Property is defined
'DesiredPropertyPropertyis the name of your property (i.e.TextProperty`
dependencyObject is the instance of object where the DesiredPropertyProperty is set on
OnDesiredPropertyChanged method to call when the property value changes
On a side note:
You should have Code-Behind in the view. There is no requirement that Dependency Properties or Attached Properties have to be defined inside the same class as the are meant for.
Code behind should only be used, if it's an reusable User Control but the naming of your class suggest it's not a User Control (even though it derives form User Control) but a View.
A view is application specific and can't be reused outside that specific app and is only meant to display a certain content. If you make a "LoginControl" then it it can be made to be reusable in other. A "LoginView" on other side doesn't suggest re-usability.
Maybe
Mode=TwoWay, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged
on the binding missing?

WPF Custom Control Error

I have some problems with a WPF custom control, I'm trying to make it work but just don't get it:
Here is my problem, I'm creating a simple custom control that's almost the same to a TextBox. This control has a dependency property named "Texto", and the binding between the XAML and back-code of the custom control works fine, here is the code:
<UserControl x:Class="WpfCustomControlLibrary1.UserControl1"
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="47" d:DesignWidth="147">
<Grid Height="43" Width="142">
<TextBox Height="23" HorizontalAlignment="Left" Margin="12,8,0,0" Name="textBox1" VerticalAlignment="Top" Width="120" Text="{Binding Texto}"/>
</Grid>
And the dependency property code:
public static readonly DependencyProperty TextoProperty = DependencyProperty.Register("Texto", typeof(string), typeof(UserControl1));
public string Texto
{
get
{
return (string)GetValue(TextoProperty);
}
set
{
SetValue(TextoProperty, value);
}
}
Ok, now the problem: When I use this control in other windows I try to bind the "Texto" property to a viewmodel (as simple as everything else) but the property on the view model just dont change:
The ViewModel code:
public class ViewModelTest
{
public string SomeText { get; set; }
}
And the code of the applicatoin Window:
public ViewModelTest test;
public Window1()
{
InitializeComponent();
}
private void button1_Click_1(object sender, RoutedEventArgs e)
{
MessageBox.Show(test.SomeText);
MessageBox.Show(uc.Texto);
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
test = new ViewModelTest();
this.DataContext = test;
}
And the binding with the property of the view model:
<my:UserControl1 HorizontalAlignment="Left" Margin="27,12,0,0" Name="uc" VerticalAlignment="Top" Texto="{Binding Path=SomeText,Mode=TwoWay}"/>
Just for make it clearer, if I write "Hello" in the custom control and then I push the "button1", the first message shows nothing and the second message shows "Hello".
As you can see I'm fairly new into this, so I hope some of you can help me. Thanks.
Your binding Texto="{Binding SomeText}" works fine, the problem is the rebinding from your user control to the inner textbox. Remember binding will ALWAYS, if not modified, refere to the DataContext. But your DataContext doesn't contain the property Texto. Your control has that, To refere to that you need something called TemplateBinding, but this only works when you are in a ControlTemplate. Which you aren't so what is the solution?
You can use a special form of binding, by changing the source from the DataContext to a control with a given name: First give your UserControl a name
<UserControl x:Class="WpfCustomControlLibrary1.UserControl1"
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"
x:Name="mainControl"
d:DesignHeight="47" d:DesignWidth="147">
and now change the binding to refere to the control, not the DataContext of the control anymore.
<TextBox Text="{Binding ElementName=mainControl, Path=Texto}"/>
Now your ViewModel binds to your user control and the content of the user control binds to the user controls Texto property.
Also one minor thing, what you called custom control, is in fact a user control, custom controls are something else.

WPF Binding to variable / DependencyProperty

I'm playing around with WPF Binding and variables. Apparently one can only bind DependencyProperties. I have come up with the following, which works perfectly fine:
The code-behind file:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public string Test
{
get { return (string)this.GetValue(TestProperty); }
set { this.SetValue(TestProperty, value); }
//set { this.SetValue(TestProperty, "BBB"); }
}
public static readonly DependencyProperty TestProperty = DependencyProperty.Register(
"Test", typeof(string), typeof(MainWindow), new PropertyMetadata("CCC"));
private void button1_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(Test);
Test = "AAA";
MessageBox.Show(Test);
}
}
XAML:
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
Title="MainWindow" Height="350" Width="525"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<TextBox Height="31" HorizontalAlignment="Left" Margin="84,86,0,0" Name="textBox1" VerticalAlignment="Top" Width="152"
Text="{Binding Test, Mode=TwoWay, diag:PresentationTraceSources.TraceLevel=High}"/>
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="320,85,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
<TextBox Height="31" HorizontalAlignment="Left" Margin="84,138,0,0" Name="textBox2" Text="{Binding Test, Mode=TwoWay}" VerticalAlignment="Top" Width="152" />
</Grid>
The two TextBoxes update one an other. And the Button sets them to "AAA".
But now I replaced the Setter function with the one that is commented out (simulating some manipulation of the given value). I would expect that whenever the property value is changed it will be reset to "BBB". It does so when you press the button, that is when you set the property in code. But it does for some reason not affect the WPF Bindings, that is you can change the TextBox contents and thus the property, but apparently the Setter is never called.
I wonder why that is so, and how one would go about to achive the expected behaviour.
The CLR Property wrapper for a Dependency Property is never guaranteed to be called and therefore, you should never place any additional logic there. Whenever you need additional logic when a DP is changed, you should use the property changed callback.
In your case..
public string Test
{
get { return (string)this.GetValue(TestProperty); }
set { this.SetValue(TestProperty, value); }
}
public static readonly DependencyProperty TestProperty =
DependencyProperty.Register("Test",
typeof(string),
typeof(MainWindow),
new PropertyMetadata("CCC", TestPropertyChanged));
private static void TestPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
MainWindow mainWindow = source as MainWindow;
string newValue = e.NewValue as string;
// Do additional logic
}
Your change will not affect the binding because the XAML will call SetValue directly, instead of calling your property setter.That is how the dependency property system works.When a dependency property is registered a default value can be specified.This value is returned from GetValue and is the default value for your property.
Check the link below and read through to Robert Rossney's post to get a fair overview
WPF: What distinguishes a Dependency Property from a regular CLR Property?
also don't miss
http://msdn.microsoft.com/en-us/library/ms753358.aspx
and
http://msdn.microsoft.com/en-us/library/ms752914.aspx
Also note that unlike in normal CLR properties any custom logic you write in the setter will not be executed in Dependency Properties,instead you have to use the PropertyChangedCallback mechanism
http://blogs.msdn.com/b/delay/archive/2010/03/23/do-one-thing-and-do-it-well-tip-the-clr-wrapper-for-a-dependencyproperty-should-do-its-job-and-nothing-more.aspx

Categories