WPF: UserControl with Dependency Property and sub-controls - c#

I'm trying to create my own, very simple, Usercontrol in WPF. It's basically just a Combobox, with some additional Logic in it.
I tried to create my own Depdency-Property using this Tutorial: http://www.codeproject.com/Articles/140620/WPF-Tutorial-Dependency-Property
This is working fine so far, but if the property changes, I'd like to reflect this on the Combobox in the User-Control as well. It seems like I can't bind the subcontrol directly to my new Dependency-Project.
My code is looking like this at the moment:
public partial class ClassSelector : UserControl
{
public static readonly DependencyProperty CurrentValueProperty =
DependencyProperty.Register("CurrentValue", typeof(ClassType),
typeof(ClassSelector), new FrameworkPropertyMetadata());
public ClassType CurrentValue
{
get
{
return (ClassType)this.GetValue(CurrentValueProperty);
}
set
{
this.SetValue(CurrentValueProperty, value);
}
}
public ClassSelector()
{
this.DataContext = this;
InitializeComponent();
cmbClassType.ItemsSource = Enum.GetValues(typeof(ClassType));
}
}
Setting the value of the dependy-property or the Combobox seems weirds to me.
I tried to bind it direclty in the xaml via:
<Grid>
<ComboBox x:Name="cmbClassType" SelectedItem="{Binding Path=CurrentValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectionChanged="cmbClassType_SelectionChanged" />
</Grid>
I tried to map the Dependicy-Project changed Event with the combobox and visa versa, but this leads to very strange code, since the combobox change would have to change the property-value and the property-value the combobox.
I'm quite sure there has to be a possibility to bind a DependencyProperty to a subcontrol, but I can't find a way to make this work.
Thanks in advance for all advices guys and have a nice weekend
Matthias
Edith says: The calling Window needs to bind the Object to the Grid, not to the Window, so for example:
grdMain.DataContext = new DeckSearch();
is working fine, meanwhile
this.DataContext = new DeckSearch();
This behavior is ONLY at my custom control, all other controls worked perfectly fine with the DataContext on the Window itself.

Okay so here I fixed your code and it is working at my end
UserControlCodeBehind
public partial class ClassSelector : UserControl
{
public static readonly DependencyProperty CurrentValueProperty = DependencyProperty.Register("CurrentValue", typeof(ClassType),
typeof(ClassSelector), new FrameworkPropertyMetadata()
{
DefaultValue = ClassType.Type1,
BindsTwoWayByDefault = true,
PropertyChangedCallback = CurrentValueChanged,
DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
});
private static void CurrentValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var obj = (ClassSelector)d;
obj.cmbClassType.SelectedValue = e.NewValue;
}
public ClassType CurrentValue
{
get
{
return (ClassType)this.GetValue(CurrentValueProperty);
}
set
{
this.SetValue(CurrentValueProperty, value);
}
}
public ClassSelector()
{
InitializeComponent();
cmbClassType.ItemsSource = Enum.GetValues(typeof(ClassType));
cmbClassType.SelectedValue = CurrentValue;
}
}
The Xaml part of the UserControl
<Grid>
<ComboBox x:Name="cmbClassType" SelectedValue="{Binding Path=CurrentValue}"/>
</Grid>
Please check if it is working at your end. I have not added any extra code checking for thread safety and all.
EDIT
In my solution I do get my Class Property notification when the CurrentValue changes.
Below is my sample MainWindow Code.
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
Task.Run(() =>
{
Thread.Sleep(1000);
Dispatcher.InvokeAsync(() =>
{
customCombobox.CurrentValue = ClassType.Type3;//Updating the UserControl DP
});
Thread.Sleep(2000);
this.CurrentValue = ClassType.Type2;//Updating my local Property
});
}
private ClassType _currentValue;
public ClassType CurrentValue
{
get { return _currentValue; }
set
{
_currentValue = value;
Debug.WriteLine("Value Changed to " + value.ToString());
RaisePropertyChanged("CurrentValue");
}
}
public event PropertyChangedEventHandler PropertyChanged;
void RaisePropertyChanged(string propName)
{
var pc = PropertyChanged;
if (pc != null)
pc(this, new PropertyChangedEventArgs(propName));
}
}
And my MainWindow.xaml
<Window x:Class="WpfApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication"
Title="MainWindow" Height="250" Width="525">
<local:ClassSelector x:Name="customCombobox" Height="25" CurrentValue="{Binding CurrentValue}"/>
</Window>

Related

Unable to bind properties using custom controls in UWP in a ListView

I have a ListView in UWP which displays a list of custom controls CustomControl. Reading around I have seen that other users have face similar issues and their solution mostly revolved around setting the DataContext of their controls, but I cannot understand how I can do that in my example. In order to dynamically update the view I used DependencyProperties in my model which is the following:
public class DataObject : DependencyObject
{
public string Name
{
get { return (string)GetValue(nameProperty); }
set { SetValue(nameProperty, value); }
}
// Using a DependencyProperty as the backing store for name. This enables animation, styling, binding, etc...
public static readonly DependencyProperty nameProperty =
DependencyProperty.Register("Name", typeof(string), typeof(DataObject), new PropertyMetadata("Name"));
}
Then in my main page I implemented the following logic to change the Name of my third element:
public sealed partial class MainPage : Page
{
private readonly ObservableCollection<DataObject> dataList;
public MainPage()
{
this.InitializeComponent();
this.dataList = new ObservableCollection<DataObject>();
for (int i = 0; i < 5; i++)
{
DataObject dataObject = new DataObject();
dataObject.Name = "Item " + i.ToString();
this.dataList.Add(dataObject);
}
DataListView.ItemsSource = dataList;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var obj = dataList.ElementAt(2);
obj.Name = "Hello!";
}
}
The XAML for the main page is the following:
<Page
x:Class="ListViewTest.MainPage"
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:controls="using:ListViewTest.Controls"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<ListView Name="DataListView">
<ListView.ItemTemplate>
<DataTemplate>
<controls:CustomControl DisplayName="{Binding Name}"></controls:CustomControl>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Content="Button" HorizontalAlignment="Center" Height="92" Width="238"
Click="Button_Click"/>
</Grid>
</Page>
The custom control CustomControl is this:
namespace ListViewTest.Controls
{
public sealed partial class CustomControl : UserControl
{
public string DisplayName
{
get { return (string)GetValue(DisplayNameProperty); }
set {
SetValue(DisplayNameProperty, value);
DisplayText.Text = value;
}
}
// Using a DependencyProperty as the backing store for DisplayName. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DisplayNameProperty =
DependencyProperty.Register("DisplayName", typeof(string), typeof(CustomControl), new PropertyMetadata("DisplayText"));
public CustomControl()
{
this.InitializeComponent();
}
}
Its structure is very simple:
<Grid>
<Button Name="ClickButton" Content="Button" Margin="171,165,0,0" VerticalAlignment="Top"/>
<TextBlock Name="DisplayText" HorizontalAlignment="Center" TextWrapping="Wrap" VerticalAlignment="Center"/>
</Grid>
The problem is that when I click the button nothing happens and I am struggling to understand why.
DataObject shouldn't inherit from DependencyObject. It should be defined as a CLR object that implements INotifyPropertyChanged:
public class DataObject : INotifyPropertyChanged
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; OnPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Also, the setter of the CLR wrapper for the dependency property in CustomControls should only set the value of the dependency property. You could set the value of the TextBlock using a PropertyChangedCallback:
public sealed partial class CustomControl : UserControl
{
public string DisplayName
{
get { return (string)GetValue(DisplayNameProperty); }
set { SetValue(DisplayNameProperty, value); }
}
public static readonly DependencyProperty DisplayNameProperty =
DependencyProperty.Register(nameof(DisplayName), typeof(string), typeof(CustomControl),
new PropertyMetadata("DisplayText", new PropertyChangedCallback(OnChanged)));
private static void OnChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CustomControl customControl = (CustomControl)d;
d.DisplayText = e.NewValue as string;
}
public CustomControl()
{
this.InitializeComponent();
}
}

How to properly expose properties of internal controls?

I have a custom control named CustomTreeView which contains a TreeView. I would like to expose the SelectedItem Property of the TreeView to users of the custom control.
For that I tried to add a new dependency property to the custom control and bind that property to the SelectedItem Property of the TreeView.
Unfortunatly I seem to be getting it wrong. Could you take a look?
TreeView.xaml
<UserControl x:Class="Fis.UI.Windows.BacNet.Views.RestructuredView.View.Controls.CustomTreeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Selected="{Binding ElementName=treeView, Path=SelectedItem}">
<TreeView x:Name="treeView"/>
</UserControl>
TreeView.xaml.cs
public partial class CustomTreeView : UserControl
{
public static readonly DependencyProperty IsSelectedProperty = DependencyProperty.Register(
"Selected", typeof(Node),
typeof(TreeView)
);
public Node Selected
{
get { return (Node)GetValue(IsSelectedProperty); }
set { SetValue(IsSelectedProperty, value); }
}
public TreeView()
{
InitializeComponent();
}
}
Thanks!
Part of your solution is in this answer here. It's a link-only answer with a dead link (or was -- I just improved it), but it does mention the key point.
You can't bind TreeView.SelectedItem. Your dependency property definition is broken in multiple ways and it should be named SelectedItem in accordance with standard WPF practice. Here's the usercontrol code behind, which defines the dependency property along with event handlers as a substitute for the binding.
public partial class CustomTreeView : UserControl
{
public CustomTreeView()
{
InitializeComponent();
}
#region SelectedItem Property
public Node SelectedItem
{
get { return (Node)GetValue(SelectedProperty); }
set { SetValue(SelectedProperty, value); }
}
public static readonly DependencyProperty SelectedProperty =
DependencyProperty.Register(nameof(SelectedItem), typeof(Node), typeof(CustomTreeView),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
Selected_PropertyChanged)
{ DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged });
protected static void Selected_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as CustomTreeView).OnSelectedChanged(e.OldValue);
}
private void OnSelectedChanged(object oldValue)
{
if (SelectedItem != treeView.SelectedItem)
{
var tvi = treeView.ItemContainerGenerator.ContainerFromItem(SelectedItem) as TreeViewItem;
if (tvi != null)
{
tvi.IsSelected = true;
}
}
}
#endregion SelectedItem Property
private void treeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
if (SelectedItem != e.NewValue)
{
SelectedItem = e.NewValue as Node;
}
}
}
And here's the TreeView in the UserControl XAML. I'm omitting ItemsSource and ItemTemplate as irrelevant.
<TreeView
x:Name="treeView"
SelectedItemChanged="treeView_SelectedItemChanged"
/>
And here's the snippet from MainWindow.xaml I used for testing it:
<StackPanel>
<local:CustomTreeView x:Name="treeControl" />
<Label
Content="{Binding SelectedItem.Text, ElementName=treeControl}"
/>
</StackPanel>
My Node class has a Text property and I'm populating the tree via the DataContext to save having to set up another dependency property for the items.

WPF DataBinding Issues - Possible Noob Problems

I am trying to bind a ViewModel property of type Visibility to the visibility property on a Dock Panel:
Updated ViewModel Code:
public class SelectWaferButtonViewModel : INotifyPropertyChanged
{
private bool isClicked;
public SelectWaferButtonViewModel()
{
isClicked = false;
}
public bool IsControlVisible
{
get
{
return isClicked;
}
set
{
isClicked = value;
OnPropertyChanged("IsControlVisible");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnButtonClick()
{
if (isClicked)
{
IsControlVisible = false;
}
else
{
IsControlVisible = true;
}
}
protected virtual void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
and here is my updated XAML code:
<DockPanel
Name="tvwDockPanel"
Width="200"
Visibility="{Binding IsControlVisible, FallbackValue=Collapsed, Converter={StaticResource BoolToVisConverter}}"
DockPanel.Dock="Left">
<DockPanel
DockPanel.Dock="Top"
Height="22">
</DockPanel>
and I set the data context in the code behind with this line:
tvwDockPanel.DataContext = btnSelectWaferViewModel;
where btnSelectWaferViewModel is the ViewModel object for this situation.
and for fun, here is my code behind:
public partial class WaferTrackerWindow : Window
{
List<ISubscribeEvents> subscriptionList;
SelectWaferButtonViewModel btnSelectWaferViewModel;
public WaferTrackerWindow()
{
InitializeComponent();
this.InstantiateObjects();
this.SubscribeEvents();
this.SetDataContexts();
}
#region Methods
private void SetDataContexts()
{
tvwDockPanel.DataContext = btnSelectWaferViewModel.IsControlVisible;
}
private void SubscribeEvents()
{
foreach (ISubscribeEvents subscriber in subscriptionList)
{
subscriber.SubscribeEvents();
}
}
private void InstantiateObjects()
{
btnSelectWaferViewModel = new SelectWaferButtonViewModel();
subscriptionList = new List<ISubscribeEvents>();
subscriptionList.Add(
new Classes.WaferTrackerWindow.SelectWaferButtonView(btnSelectWafer, btnSelectWaferViewModel));
}
#endregion
}
All I want to do click the button btnSelectWafer and have the tvwDockPanel's visibility property to get to set to Visible via binding. Then when you click again on btnSelectWafer, tvwDockPanel's visibility property gets set back to Collapsed again. tvwDockPanel's visibility will only ever be either Collapsed or Visible.
Any help would be awesome, I am rather new to this whole data binding concept.
You have several issues here:
First of all, the intent of MVVM (if you're trying to do this with MVVM) is to separate logic from presentation. This means that in no way your ViewModel can have a reference to System.Windows.Controls.Button, nor to System.Windows.Visibility, nor to any other classes inside the System.Windows Namespace.
It is not clear to me what your SelectWaferButtonViewModel class is doing with the Button, but you need to remove the Button from there.
Also, If you need to manipulate the Visibility of a control from the ViewModel layer, you'd better use a Boolean property and the BooleanToVisibilityConverter in XAML:
ViewModel:
public bool IsControlVisible {get;set;} //Don't forget INotifyPropertyChanged!!
XAML:
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisConverter"/>
</Window.Resources>
<DockPanel Visibility="{Binding IsControlVisible, Converter={StaticResource BoolToVisConverter}}"/>
The problem is that you're binding your DockPanel to the boolean property of your view model, and then setting the Visiblity property of your UI element to the IsControlVisible property of the datacontext (which doesn't exist).
Change to:
private void SetDataContexts()
{
tvwDockPanel.DataContext = btnSelectWaferViewModel;
}

Chain of DataBinding

I am trying to do follow DataBinding
Property -> DependencyProperty -> Property
But i have trouble.
For example,
We have simple class with two properties implements INotifyPropertyChanged:
public class MyClass : INotifyPropertyChanged
{
private string _num1;
public string Num1
{
get { return _num1; }
set
{
_num1 = value;
OnPropertyChanged("Num1");
}
}
private string _num2;
public string Num2
{
get { return _num2; }
set
{
_num2 = value;
OnPropertyChanged("Num2");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(e));
}
}
And TextBlock declared in xaml:
<TextBlock Name="tb" FontSize="20" Foreground="Red" Text="qwerqwerwqer" />
Now lets trying to bind Num1 to tb.Text:
private MyClass _myClass = new MyClass();
public MainWindow()
{
InitializeComponent();
Binding binding1 = new Binding("Num1")
{
Source = _myClass,
Mode = BindingMode.OneWay
};
Binding binding2 = new Binding("Num2")
{
Source = _myClass,
Mode = BindingMode.TwoWay
};
tb.SetBinding(TextBlock.TextProperty, binding1);
//tb.SetBinding(TextBlock.TextProperty, binding2);
var timer = new Timer(500) {Enabled = true,};
timer.Elapsed += (sender, args) => _myClass.Num1 += "a";
timer.Start();
}
It works well. But if we uncomment this string
tb.SetBinding(TextBlock.TextProperty, binding2);
then TextBlock display nothing. DataBinding doesn't work! How can i to do what i want?
The problem is that the SetBinding call clears out any previous bindings. So when you set a binding to Num2, you are clearing out the binding to Num1. This happens because a dependency property binding cannot have multiple sources- how would it know which one to use? (Of course, this ignores the usage of a MultiBinding, but that's not going to help you in this scenario).
The way you can do this is to make MyClass a DependencyObject and Num1 and Num2 dependency properties. Then you can bind Num2 to the Text property of the TextBox, and Num2 will be updated whenever the text receives an update from Num1.
A picture is worth a thousand words- what you're trying to do is shown on the left. What you need to do is shown on the right:
alt text http://img339.imageshack.us/img339/448/twosources.png
Decided to try this out to ensure my logic was sound, and indeed it works, but there are some tricks. For starters, here is the new MyClass code:
public class MyClass : FrameworkElement
{
public static readonly DependencyProperty Num1Property =
DependencyProperty.Register("Num1", typeof(string), typeof(MyClass));
public static readonly DependencyProperty Num2Property =
DependencyProperty.Register("Num2", typeof(string), typeof(MyClass));
public string Num1
{
get { return (string)GetValue(Num1Property); }
set { SetValue(Num1Property, value); }
}
public string Num2
{
get { return (string)GetValue(Num2Property); }
set { SetValue(Num2Property, value); }
}
}
Nothing scary here, just replaced your INotifyPropertyChanged with DependencyProperty. Now let's check out the window code-behind:
public partial class DataBindingChain : Window
{
public MyClass MyClass
{
get;
set;
}
public DataBindingChain()
{
MyClass = new MyClass();
InitializeComponent();
Binding binding1 = new Binding("Num1")
{
Source = MyClass,
Mode = BindingMode.OneWay
};
Binding binding2 = new Binding("Text")
{
Source = tb,
Mode = BindingMode.OneWay
};
tb.SetBinding(TextBlock.TextProperty, binding1);
MyClass.SetBinding(MyClass.Num2Property, binding2);
var timer = new Timer(500) { Enabled = true, };
timer.Elapsed += (sender, args) => Dispatcher.Invoke(UpdateAction, MyClass);
timer.Start();
}
Action<MyClass> UpdateAction = (myClass) => { myClass.Num1 += "a"; };
}
This is where the magic happens: we set up two bindings. The first binds the TextBlock.Text to Num1, the second binds Num2 to the TextBlock.Text. Now we have a scenario like the right side of the picture I showed you- a data-binding chain. The other magic is that we cannot update the Num1 property on a different thread from the one it was created on- that would create a cross-thread exception. To bypass this, we simply invoke an update onto the UI thread using the Dispatcher.
Finally, the XAML used for demonstration:
<Window x:Class="TestWpfApplication.DataBindingChain"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DataBindingChain" Height="300" Width="300"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Name="tb" Grid.Row="0" FontSize="20" Foreground="Red"/>
<TextBlock Name="tb2" Grid.Row="1" FontSize="20" Foreground="Blue" Text="{Binding MyClass.Num2}"/>
</Grid>
And voila! The finished product:
alt text http://img163.imageshack.us/img163/6114/victorynf.png

How to create a UserControl in the MVVM pattern?

Currently, within a real-world application development, I am struggling with the consumption of a custom UserControl in the MVVM pattern.
In my application, there is a DataGrid where the user can select an entry. The DataGrid's SelectedItem is TwoWay-bound to a field of the ViewModel set as DataContext. When the user selects an entry, the field is properly updated (tested). In the Page where holds the DataGrid, the field is bound through XAML to a DependencyProperty of a custom UserControl devised in the MVVM pattern : it bares its own ViewModel which is set as DataContext. The trouble is that the UserControl's DependencyProperty is not updated when the field changes even though the INotifyPropertyChanged interface is correctly implemented (see the comparison with a traditional control in the next minimal working example).
This example is constituted of a Label and bares ViewModelUserControl as a DataContext, UserControl1is consumed by the MainWindow and the binding is compared to that of a Label.
The file MainWindow.xaml:
<Window x:Class="UserControlWithinUserControlDataContext.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Local="clr-namespace:UserControlWithinUserControlDataContext"
Title="MainWindow"
Height="350" Width="525"
>
<StackPanel Orientation="Horizontal"
>
<ListBox SelectedItem="{Binding Text, Mode=TwoWay}"
x:Name="listbox"
Height="150"
>
</ListBox>
<Local:UserControl1 Text="{Binding Text, Mode=OneWay}"
Height="50" Width="150"
/>
<Label Content="{Binding Text, Mode=OneWay}"
/>
</StackPanel>
</Window>
The code-behind MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public ViewModelWindow view_model_window
{
get { return _view_model; }
}
private ViewModelWindow _view_model = new ViewModelWindow();
public MainWindow()
{
InitializeComponent();
DataContext = view_model_window;
IList<String> list = new List<String>();
list.Add("A");
list.Add("B");
list.Add("C");
listbox.ItemsSource = list;
}
}
The ViewModel of the MainWindow, the file ViewModelWindow.cs :
public class ViewModelWindow : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public String Text
{
get { return text; }
set
{
if (text != value)
{
text = value;
NotifyPropertyChanged("Text");
}
}
}
private String text = "Bli";
}
The file UserControl1.xaml:
<UserControl x:Class="UserControlWithinUserControlDataContext.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="300" d:DesignWidth="300">
<Grid>
<Label Content="{Binding Text}"
Background="Magenta"
HorizontalAlignment="Stretch"
/>
</Grid>
</UserControl>
The code-behind file UserControl1.xaml.cs:
public partial class UserControl1 : UserControl
{
public ViewModelUserControl view_model_usercontrol
{
get { return _view_model; }
}
private ViewModelUserControl _view_model = new ViewModelUserControl();
public String Text
{
get { return (String)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(String), typeof(UserControl1),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(TextPropertyChangedCallback)));
private static void TextPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UserControl1 user_control = d as UserControl1;
if(user_control != null)
{
user_control.view_model_usercontrol.Text = user_control.Text;
}
}
public UserControl1()
{
InitializeComponent();
DataContext = view_model_usercontrol;
}
}
The ViewModel of UserControl1, the file ViewModelUserControl.cs:
public class ViewModelUserControl : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public String Text
{
get { return text; }
set
{
if (text != value)
{
text = value;
NotifyPropertyChanged("Text");
}
}
}
private String text = "";
}
As you can see when executing this code, the MainWindow's Label gets updated while the UserControl1's Label doesn't.
What am I doing wrong? Is there a way to makes this works?
Many thanks in advance for any clue.
first you do not need to add anything in the UserControl just the XAML. Remove all the code of the UserControl and try.
Let's explain why:
Content="{Binding Text}" you set this in the usercontrol xaml, it's binded to the ViewModelWindow. and that works. and remove in
<Local:UserControl1 => Text="{Binding Text, Mode=OneWay}"
Ok, but it is correct to define a property in the user control in case of other situation?, that's right, in order to do that:
<UserControl x:Name="UserControlInstance"...>
<Label Content="{Binding Text, ElementName=UserControlInstance}" ...>
Where in this case Text is the dependency property and not the datacontext property.
Try the first option, and then the second defining just a Dependency Property and in this case bind the dependency property as you did.
And a tip, if a dependency property is in the visual element tree like in your case you do not need to call the callback.
Thank you Juan for your answer, here is the solution for conceiving the UserControl in the MVVM pattern:
I gave the name root to the Grid of UserControl1 and set its DataContext:
root.DataContext = view_model_usercontrol;
instead of:
DataContext = view_model_usercontrol;
Everything works fine.
Happy ending :)

Categories

Resources