I have the following xaml;
<DataTemplate DataType="{x:Type NameSpace:Node}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Item.Value}"/>
<ContentControl Content="{Binding Item}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type NameSpace:Item}">
<TextBlock Text="{Binding Value}" />
</DataTemplate>
When I use the Node template to display a Node object, I get two TextBlocks which both display the same value. So far so good. The problem occurs when Item changes. When the Node class fires the INotifyPropertyChanged event, the TextBlock in the Node DataTemplate updates as expected but the TextBlock in the Item DataTemplate does not update.
How can I get the Item DataTemplate to update its bindings when the Node class fires the IPropertyChanged event?
Update
It turns out the above does work for the following simple scenario;
Xaml
<DataTemplate DataType="{x:Type DataTemplateExample:Node}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Item.Value}"/>
<ContentControl Content="{Binding Item}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type DataTemplateExample:Item}">
<TextBlock Text="{Binding Value}" />
</DataTemplate>
</Window.Resources>
<Grid>
<StackPanel Orientation="Vertical">
<ContentControl Content="{Binding MyNode}"/>
<Button Command="{Binding ChangeMyNodeItem}">Change Item</Button>
</StackPanel>
</Grid>
</Window>
c#
public class MainViewModel
{
private readonly Node myNode;
private readonly DelegateCommand changeMyNodeItemCmd;
public MainViewModel()
{
myNode = new Node {Item = new Item("a")};
changeMyNodeItemCmd = new DelegateCommand(()=>
myNode.Item = new Item("b"));
}
public Node MyNode { get { return myNode; } }
public ICommand ChangeMyNodeItem
{
get { return changeMyNodeItemCmd; }
}
}
public class Node : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private Item item;
public Item Item
{
set
{
item = value;
if (PropertyChanged != null)
PropertyChanged(this,new PropertyChangedEventArgs("Item"));
}
get { return item; }
}
}
public class Item
{
private readonly string value;
public Item(string value)
{
this.value = value;
}
public string Value
{
get { return value; }
}
}
In my real scenario I was using proxies, and I think this is what was getting WPF confused. Item was not actually changing - it was being remapped.
Ultimately I solved the problem using a solution similar to what ShadeOfGray proposed. But I should point out that this is not necessary unless you are using proxies.
From what you have posted I think you're firing the NotifyPropertyChanged in the wrong class. Something like that should work properly in your scenario.
Updated according to the comment:
public class Node : INotifyPropertyChanged
{
private Item item;
public event PropertyChangedEventHandler PropertyChanged;
public Item Item
{
get
{
return item;
}
set
{
item = value;
this.NotifyPropertyChanged("Item");
if (item != null)
{
item.ForcePropertyChanged("Value");
}
}
}
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class Item : INotifyPropertyChanged
{
private string itemValue;
public Item()
{
this.Value = string.Empty;
}
public event PropertyChangedEventHandler PropertyChanged;
public string Value
{
get
{
return itemValue;
}
set
{
itemValue = value;
NotifyPropertyChanged("Value");
}
}
public void ForcePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Related
<GridView AllowsColumnReorder="False">
<GridViewColumn Header="Tên Mặt Hàng" CellTemplate="{StaticResource Ten}">
<GridViewColumn.CellTemple>
<DataTemplate x:Key="Ten">
<TextBlock Text="{Binding Ten}"></TextBlock>
</DataTemplate>
</GridViewColumn.CellTemple>
</GridViewColumn> </GridView>
every one, I have code, I want to binding Text of textBlock to use data again in other files, so what can I do?
You can create a ViewModel of this view. And bind the viewModel property to this view. For example:
public class NamViewModel: INotifyPropertyChanged {
private string _ten;
public string Ten {
get
{
return _ten;
}
set
{
_ten = value;
this.RaisePropertyChanged("Ten");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then you can bind this viewModel in the constructor of the view. For example:
public partial class NamView : Window {
public NamView() {
InitializeComponent();
this.DataContext = new NamViewModel();
}
}
I have below class hierarchy.
public class RowViewModel : BaseViewModel
{
private CellViewModel cell;
public CellViewModel Cell
{
get { return cell; }
set
{
cell = value;
OnPropertyChanged("Cell");
}
}
}
public class CellViewModel : BaseViewModel
{
public string Text { get { return string.Join("\n", CellLinks.Select(c => c.Text)); } }
private ObservableCollection<CellLinkViewModel> cellLinks;
public ObservableCollection<CellLinkViewModel> CellLinks
{
get
{ return cellLinks; }
set
{
cellLinks = value;
OnPropertyChanged("CellLinks");
}
}
}
public class CellLinkViewModel : BaseViewModel
{
public string Text
{
get { return CellValue.Text; }
set
{
CellValue.Text = value;
OnPropertyChanged("Text");
}
}
public CellValueViewModel CellValue { get; set; }
}
public class CellValueViewModel : BaseViewModel
{
public string Text { get; set; }
}
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyName);
PropertyChanged(this, e);
}
}
}
XAML code looks like,
<DataGrid ItemsSource="{Binding Rows}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="#" Width="200">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Cell.Text}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding Cell.CellLinks}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Text, Mode=TwoWay}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
CellValueViewModel is shared across multiple CellLinkViewModel. When I change CellValueViewModel it should have propagated changes to all dependent parents and DataGrid should show all latest values.
This is result
But I think its not happening automatically, am I missing something here? How to notify all datagrid cells to update automatically when the nested objects gets updated.
You need to hook up event handlers to the PropertyChanged event for all CellLinkViewModel objects and raise the PropertyChanged event for the Text property of the CellViewModel whenever a link is modified, e.g.:
public class CellViewModel : BaseViewModel
{
public string Text { get { return string.Join("\n", CellLinks.Select(c => c.Text)); } }
private ObservableCollection<CellLinkViewModel> cellLinks;
public ObservableCollection<CellLinkViewModel> CellLinks
{
get
{
return cellLinks;
}
set
{
cellLinks = value;
if(cellLinks != null)
{
foreach(var link in cellLinks)
{
link.PropertyChanged += Link_PropertyChanged;
}
}
OnPropertyChanged("CellLinks");
}
}
private void Link_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
OnPropertyChanged("Text");
}
}
You probably also want to handle the CollectionChanged event for the ObservableCollection<CellLinkViewModel> and hook up event handlers to dynamically added CellLinkViewModel objects as well.
I would like to bind the IsEnabled property of TabItem to data within my code.
e.g. I have a TabItem defined as follows
<TabItem Name="Tab1" Header="Tab1" IsEnabled="{Binding Path=Tab1Enabled, Mode=TwoWay}">
</TabItem>
And I have defined a data class which inherits from INotifyPropertyChanged as follows
class MyData : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
public bool Tab1Enabled
{
get{ return m_tab1Enabled; }
set
{
m_tab1Enabled = value;
OnPropertyChanged("Tab1Enabled");
}
}
}
Then I set the DataContext to my data member.
public partial class MyApp : Window
{
MyData m_myData = new MyData();
MyApp()
{
InitializeComponent();
this.DataContext = m_myData;
}
}
However, when the Tab1Enabled property gets set programatically, the PropertyChanged event is null and so the notification event is not sent.
Thanks in advance.
Im guessing you need to change m_bindinData to the variable you want to bind to (m_myData). I fired up VS2012 and tested your code. Setting m_myData.Tab1Enabled = true; set the tab to enabled and setting m_myData.Tab1Enabled = false; disabled it correctly. Heres what I had.
public partial class MyApp : Window
{
MyData m_myData = new MyData();
MyApp()
{
this.DataContext = m_myData;
InitializeComponent();
m_myData.Tab1Enabled = true;
}
}
class MyData : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
public bool Tab1Enabled
{
get { return m_tab1Enabled; }
set
{
m_tab1Enabled = value;
OnPropertyChanged("Tab1Enabled");
}
}
private bool m_tab1Enabled;
}
<TabControl>
<TabItem Name="Tab1" Header="Tab1" IsEnabled="{Binding Path=Tab1Enabled}">
</TabItem>
<TabItem Name="Tab2" Header="Tab2">
</TabItem>
</TabControl>
What about this? Notice the PropertyChangedEventHandler PropertyChanged = PropertyChanged; line in the OnPropertyChanged method.
class MyData : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string name)
{
PropertyChangedEventHandler PropertyChanged = PropertyChanged;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
public bool Tab1Enabled
{
get{ return m_tab1Enabled; }
set
{
m_tab1Enabled = value;
OnPropertyChanged("Tab1Enabled");
}
}
}
Your TabItem might not inheriting DataContext of your Window. Try looking into the output window, binding failure error will be there.
As a workaround you can use RelativeSource to travel upto the DataContext of your window and bind with its related property like this -
<TabItem Name="Tab1" Header="Tab1"
IsEnabled="{Binding Path=DataContext.Tab1Enabled,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=Window}, Mode=TwoWay}"/>
I am using WPF and MVVM light framework (and I am new in using them)
Here is the situation:
I have a combobox displaying a list of items (loaded from a database) and I am using the DisplayMemberPath to display the title of the items in the combobox.
In the same GUI, the user can modify the item title in a text box. A button 'Save' allows the user to save the data into the database.
What I want to do is when the user clicks 'Save', the item title in the combobox gets updated too and the new value is displayed at that time. However, I do not know how to do that...
Some details on my implementation:
MainWindow.xaml
<ComboBox ItemsSource="{Binding SourceData}" SelectedItem="{Binding SelectedSourceData,Mode=TwoWay}" DisplayMemberPath="Title" />
<TextBlock Text="{Binding SelectedDataInTextFormat}"/>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Closing += (s, e) => ViewModelLocator.Cleanup();
}
}
MainViewModel.xaml
public class MainViewModel:ViewModelBase
{
public ObservableCollection<Foo> SourceData{get;set;}
public Foo SelectedSourceData
{
get{return _selectedFoo;}
set{_selectedFoo=value; RaisePropertyChanged("SelectedSourceData"); }
}
public string SelectedDataInTextFormat
{
get{return _selectedDataInTextFormat;}
set{_selectedDataInTextFormat=value; RaisePropertyChanged("SelectedDataInTextFormat");
}
}
I would appreciate if anyone could help me on this one.
Thanks for your help.
Romain
You might simply update the SelectedSourceData property when SelectedDataInTextFormat changes:
public string SelectedDataInTextFormat
{
get { return _selectedDataInTextFormat; }
set
{
_selectedDataInTextFormat = value;
RaisePropertyChanged("SelectedDataInTextFormat");
SelectedSourceData = SourceData.FirstOrDefault(f => f.Title == _selectedDataInTextFormat)
}
}
EDIT: In order to change the Title property of the currently selected Foo item in the ComboBox, you could implement INotifyPropertyChanged in your Foo class:
public class Foo : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string title = string.Empty;
public string Title
{
get { return title; }
set
{
title = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Title"));
}
}
}
}
Then simply set the Title property of the selected item:
SelectedSourceData.Title = SelectedDataInTextFormat;
There is many ways to do this, This example takes advantage of the Button Tag property to send some data to the save button handler(or ICommand), Then we can set the TextBox UpdateSourceTrigger to Explicit and call the update when the Button is clicked.
Example:
Xaml:
<Window x:Class="WpfApplication8.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="105" Width="156" Name="UI">
<Grid DataContext="{Binding ElementName=UI}">
<StackPanel Name="stackPanel1">
<ComboBox x:Name="combo" ItemsSource="{Binding SourceData}" DisplayMemberPath="Title" SelectedIndex="0"/>
<TextBox x:Name="txtbox" Text="{Binding ElementName=combo, Path=SelectedItem.Title, UpdateSourceTrigger=Explicit}"/>
<Button Content="Save" Tag="{Binding ElementName=txtbox}" Click="Button_Click"/>
</StackPanel>
</Grid>
</Window>
Code:
public partial class MainWindow : Window, INotifyPropertyChanged
{
private ObservableCollection<Foo> _sourceData = new ObservableCollection<Foo>();
public MainWindow()
{
InitializeComponent();
SourceData.Add(new Foo { Title = "Stack" });
SourceData.Add(new Foo { Title = "Overflow" });
}
public ObservableCollection<Foo> SourceData
{
get { return _sourceData; }
set { _sourceData = value; }
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var txtbx = (sender as Button).Tag as TextBox;
txtbx.GetBindingExpression(TextBox.TextProperty).UpdateSource();
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
public class Foo : INotifyPropertyChanged
{
private string _title;
public string Title
{
get { return _title; }
set { _title = value; RaisePropertyChanged("Title"); }
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
Code:
public ObservableCollection<Foo> SourceData{get;set;}
public Foo SelectedSourceData
{
get{
return _selectedFoo;
}
set{
_selectedFoo=value;
RaisePropertyChanged("SelectedSourceData");
}
}
public string SelectedDataInTextFormat //Bind the text to the SelectedItem title
{
get{
return SelectedSourceData.Title
}
set{
SelectedSourceData.Title=value;
RaisePropertyChanged("SelectedDataInTextFormat");
}
}
XAML:
<ComboBox ItemsSource="{Binding SourceData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding SelectedSourceData,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="Title" />
<TextBlock Text="{Binding SelectedDataInTextFormat, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
I am trying to successfully TwoWay bind an ObservableCollection to TextBoxes in a DataTemplate. I can get the data to display properly, but I am unable to change the list data through the UI. I have a Model class named 'model' which contains an ObservableCollection named 'List'. The class implements the INotifyPropertyChanged interface. Here is the xaml for the shell. The DataContext for Window1's grid is set to "theGrid.DataContext=model"
<Window x:Class="BindThat.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:BindThat"
Title="Window1" Height="300" Width="300">
<StackPanel x:Name="theGrid">
<GroupBox BorderBrush="LightGreen">
<GroupBox.Header>
<TextBlock Text="Group" />
</GroupBox.Header>
<ItemsControl ItemsSource="{Binding Path=List}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=., Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
</StackPanel>
This is the code for the Model class:
class Model : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
private ObservableCollection<string> _list = new ObservableCollection<string>();
public ObservableCollection<string> List
{
get { return _list; }
set
{
_list = value;
NotifyPropertyChanged("List");
}
}
public Model()
{
List.Add("why");
List.Add("not");
List.Add("these?");
}
}
Could anyone advise if I am going about this the correct way?
You need a property to bind two way, so string is not good for this.
Wrap it in a string object, like this:
public class Model
{
public ObservableCollection<StringObject> List { get; private set; }
public Model()
{
List = new ObservableCollection<StringObject>
{
new StringObject {Value = "why"},
new StringObject {Value = "not"},
new StringObject {Value = "these"},
};
}
}
public class StringObject
{
public string Value { get; set; }
}
and bind to Value property instead of "."
Also, you don't need to notify of a change in observable collection, so until your model has some other propertis of its own, it does not need to have INotifyPropertyChange. If you want your ItemsControl react to changes in the individual StringObjects, then you should add INotifyPropertyChanged to a StringObject.
And yet again, two way binding is default, so you need only
<TextBox Text="{Binding Path=Value}" />
in your binding.
I believe you need to derive your collection items from DependencyObject for TwoWay binding to work. Something like:
public class DependencyString: DependencyObject {
public string Value {
get { return (string)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(string), typeof(DependencyString), new UIPropertyMetadata(""));
public override string ToString() {
return Value;
}
public DependencyString(string s) {
this.Value = s;
}
}
public class Model {
private ObservableCollection<DependencyString> _list = new ObservableCollection<DependencyString>();
public ObservableCollection<DependencyString> List {
get { return _list; }
}
public Model() {
List.Add(new DependencyString("why"));
List.Add(new DependencyString("not"));
List.Add(new DependencyString("these?"));
}
}
...
<StackPanel x:Name="theGrid">
<GroupBox BorderBrush="LightGreen">
<GroupBox.Header>
<TextBlock Text="Group" />
</GroupBox.Header>
<ItemsControl ItemsSource="{Binding Path=List}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=Value, Mode=TwoWay}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
</StackPanel>
xaml view:
<ItemsControl ItemsSource="{Binding List}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
in code behind in the constructor:
DataContext = new ViewModel();
in ViewModel Class:
class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
private ObservableCollection<StringObject> _List = new ObservableCollection<StringObject>();
public ObservableCollection<StringObject> List
{
get { return _List; }
set
{
_List = value;
NotifyPropertyChanged("List");
}
}
public ViewModel()
{
List = new ObservableCollection<StringObject>
{
new StringObject {Value = "why"},
new StringObject {Value = "not"},
new StringObject {Value = "these"}
};
}
}
public class StringObject
{
public string Value { get; set; }
}
Be careful with a collection with type string it doesn't work, you have to use an object => StringObject