What is the use of implementing the INotifyPropertyChanged, when the below code is working just fine without it?
<DataGrid ItemsSource="{Binding Items}"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Name"
Binding="{Binding Name}"/>
<DataGridComboBoxColumn Header="Color"
SelectedItemBinding="{Binding Color}">
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding Colors}"/>
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding Colors}"/>
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
</DataGrid.Columns>
</DataGrid>
<Button Content="Change Colors" Click="Change"/>
public class Data
{
private ObservableCollection<Item> _items;
public ObservableCollection<Item> Items
{
get { return _items; }
}
public Data()
{
_items = new ObservableCollection<Item>();
_items.Add(new Item() { Name = "A" });
_items.Add(new Item() { Name = "B" });
}
public void Change()
{
_items[0].Colors.RemoveAt(1);
}
}
public class Item
{
public string Name { get; set; }
public string Color { get; set; }
private IList<string> _colors;
public IList<string> Colors
{
get { return _colors; }
}
public Item()
{
_colors = new List<string> { "Green", "Blue" };
Color = _colors[0];
}
}
An ObservableCollection<T> implements the INotifyCollectionChanged and INotifyPropertyChanged interfaces so if you simply want to be able to add and remove items from the source collection at runtime there is no need for you to implement the INotifyPropertyChanged interface.
You would have to implement it in your custom Item class if you wanted to be able to for example update the Name or Color property dynamically though.
If you set the Name property of an Item to a new value this won't get reflected in the view unless the Item class implements the INotifyPropertyChanged interface and raises the PropertyChanged event in the setter of the Name property.
What if I want to have a custom functionality when the collection changes! For example throw a message! Should I use List instead of ObservableCollection and throw that message in the property's `´set´?
The you could handle the CollectionChanged event of the ObservableCollection<T>.
Related
In the following class, the itemssource of a listbox should bind to the Interfaces property.
public class BaseClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private const string TYPE_TITLE = "Type";
private string _Type;
public string Type
{
get { return _Type; }
set { _Type = value; this.NotifyPropertyChanged(PropertyChanged, TYPE_TITLE); }
}
public ObservableCollection<string> Interfaces { get; set; }
public BaseClass()
{
Interfaces = new ObservableCollection<string>();
}
public void Reset()
{
_Type = null;
Interfaces.Clear();
}
}
In that list box the selected item should be able to edit as the inline edit scenario,
<DataTemplate x:Key="BaseClass_Interfaces_InlineEdit_Template">
<TextBox Text="{Binding Mode=TwoWay, Path=., NotifyOnTargetUpdated=True, UpdateSourceTrigger=PropertyChanged}" TextChanged="TextBox_TextChanged"/>
</DataTemplate>
<DataTemplate x:Key="BaseClass_Interfaces_InlineView_Template">
<TextBlock Text="{Binding Path=., UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
<Style TargetType="{x:Type ListBoxItem}" x:Key="BaseClass_Iterfaces_ItemStyle_Template">
<Setter Property="ContentTemplate" Value="{StaticResource BaseClass_Interfaces_InlineView_Template}" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="ContentTemplate" Value="{StaticResource BaseClass_Interfaces_InlineEdit_Template}" />
</Trigger>
</Style.Triggers>
</Style>
The ListBox has a container as a parent hierarchy which its DataContext property bind to an instance of BaseClass hence the ListBox could bind to the Interfaces property.
<ListBox Grid.Row="2" Grid.ColumnSpan="2" Margin="3" ItemsSource="{Binding Interfaces, Mode=TwoWay}" SelectionMode="Single"
ItemContainerStyle="{StaticResource ResourceKey=BaseClass_Iterfaces_ItemStyle_Template}" />
The list box before select any item
Editing the selected item
Another item select after edit and the changes doesn't affected
There are two problems :
The TextBox should have "Path=." otherwise the "Two-way binding requires Path or XPath." exception message received.
With consider the above problem, the ObservableCollection items never updated after text changed!!!!!!
I found the answer!
My wondering was about the text box which the text property changed but the changes does not propagated to the source, based on the link the binding mechanism works on the properties of sources in the other words the change of the properties monitors not the object itself.
The solution is a wrapper class around the string, i wrote this wrapper before for another primitive type (bool).
public class Wrapper<T>
{
public T Item { get; set; }
public Wrapper(T value = default(T))
{
Item = value;
}
public static implicit operator Wrapper<T>(T item)
{
return new Wrapper<T>(item);
}
public static implicit operator T(Wrapper<T> item)
{
if (null != item)
return item.Item;
return default(T);
}
}
So the editing data template change as follow
<DataTemplate x:Key="BaseClass_Interfaces_InlineEdit_Template">
<TextBox Text="{Binding Mode=TwoWay, Path=Item, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
And every thing work as charm!!!
I am new in wpf and mvvm databinding. Now i am trying to make crud process with data gird. I have a problem to updateing process.I want to get update value after data grid cell updated by mvvm process.
Model
public class EmployeeType : INotifyPropertyChanged
{
string _EmpType;
public string EmpType
{
get
{
return _EmpType;
}
set
{
if(_EmpType !=value)
{
_EmpType = value;
RaisePropertyChange("EmpType");
}
}
}
string _EmpTypeDesc;
public string EmpTypeDesc
{
get
{
return _EmpTypeDesc;
}
set
{
if(_EmpTypeDesc!=value)
{
_EmpTypeDesc = value;
RaisePropertyChange("EmpTypeDesc");
}
}
}
bool _OTRounding;
public bool OTRounding
{
get
{
return _OTRounding;
}
set
{
if(_OTRounding!=value)
{
_OTRounding = value;
RaisePropertyChange("OTRounding");
}
}
}
decimal _EarlyOTTimeBuffer;
public decimal EarlyOTTimeBuffer
{
get
{
return _EarlyOTTimeBuffer;
}
set
{
if(_EarlyOTTimeBuffer!=value)
{
_EarlyOTTimeBuffer = value;
RaisePropertyChange("EarlyOTTimeBuffer");
}
}
}
string _EarlyOTRounding;
public string EarlyOTRounding
{
get
{
return _EarlyOTRounding;
}
set
{
if(_EarlyOTRounding!=value)
{
_EarlyOTRounding = value;
RaisePropertyChange("EarlyOTRounding");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
void RaisePropertyChange(string prop)
{
if(PropertyChanged !=null)
{
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
View Model
class EmployeeTypeViewModel:ViewModelBase
{
private ObservableCollection<EmployeeType> _EmployeeTypeList = new ObservableCollection<EmployeeType>();
private ObservableCollection<TimeFormat> _ThreeTimeFormat = new ObservableCollection<TimeFormat>();
public ObservableCollection<TimeFormat> ThreeTimeFormat
{
get
{
return _ThreeTimeFormat;
}
set
{
_ThreeTimeFormat = value;
RaisePropertyChanged("ThreeTimeFormat");
}
}
public ObservableCollection<EmployeeType> EmployeeTypeList
{
get
{
return _EmployeeTypeList;
}
set
{
_EmployeeTypeList = value;
RaisePropertyChanged("EmployeeTypeList");
}
}
public EmployeeType _SelectedEarlyOTRounding;
public EmployeeType SelectedEarlyOTRounding
{
get
{
return _SelectedEarlyOTRounding;
}
set
{
if (_SelectedEarlyOTRounding != value)
{
_SelectedEarlyOTRounding = value;
RaisePropertyChanged("SelectedEarlyOTRounding");
}
}
}
public EmployeeTypeViewModel()
{
_EmployeeTypeList = DataAccess.EmployeeTypeDataAccessor.GetAllEmployeeTypes();
ThreeTimeFormat = TMSHelper.GetThreeTimeFormat();
}
}
View
<UserControl.Resources>
<ViewModels:EmployeeTypeViewModel x:Key="ViewModel"/>
</UserControl.Resources>
<Grid DataContext="{Binding Source={StaticResource ViewModel}}">
<DataGrid Margin="10,10,9.6,10.2" x:Name="empgrid" ItemsSource="{Binding EmployeeTypeList,Mode=TwoWay}" AutoGenerateColumns="False" >
<DataGrid.Columns>
<DataGridTextColumn Header="EmpType" Binding="{Binding EmpType,Mode=TwoWay}"/>
<DataGridCheckBoxColumn Header="OTRounding" Binding="{Binding OTRounding}"/>
<DataGridTextColumn Header="Description" Binding="{Binding EmpTypeDesc}"/>
<DataGridTextColumn Header="Early OT Time Buffer" Binding="{Binding EarlyOTTimeBuffer}"/>
<DataGridTemplateColumn Header="Early OT Time Rounding">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox SelectedValuePath="Value" DisplayMemberPath="Key" ItemsSource="{Binding Path=DataContext.ThreeTimeFormat,ElementName=empgrid}" SelectedValue="{Binding EarlyOTRounding,Mode=TwoWay}" SelectedItem="{Binding SelectedEarlyOTRounding}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
I just realized things is if I changed value in datagrid cell,It's will auto update the EmployeeTypeList in viewmodel.Because i added mode=twoway in itemsource of grid.right? But when i debug, Set of EmployeeTypeList never happen.Why? if my doing process is wrong,please let me known how to do that? If you don't understand,please let me known.Thanks.
You probably just don't understand to binding completly at this point and it is okay.
Mode=TwoWay means that binded property will be changed on the UI when value is changed in the underlying object and also when user change the value on the UI.
In your case you should have to replace collection on the UI to notice the change. So, far you are changing content of the ObservableCollection and because of that you are not getting any notification on collection level. You should have get notification for EmpType when you change it on the UI.
Is it clear?
One of the BindingMode values. The default is Default, which returns
the default binding mode value of the target dependency property.
However, the default value varies for each dependency property. In
general, user-editable control properties, such as those of text boxes
and check boxes, default to two-way bindings, whereas most other
properties default to one-way bindings.
(source: MSDN )
so generally where it is possible to edit usually does not need to inform
now, for your combobox to work correctly try this.
I usually use it and works perfectly
<DataGridComboBoxColumn Header="Early OT Time Rounding"
DisplayMemberPath="Key"
SelectedValuePath="Value"
SelectedValueBinding="{Binding EarlyOTRounding, UpdateSourceTrigger=PropertyChanged}"
>
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}, Path=DataContext.ThreeTimeFormat, UpdateSourceTrigger=PropertyChanged}"/>
<Setter Property="Width" Value="280" />
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}, Path=DataContext.ThreeTimeFormat, UpdateSourceTrigger=PropertyChanged}"/>
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
attention to this
DataGridComboBoxColumn must have a SelectedValueBinding property set to being an item in your EmployeeTypeList so that the selected value is changed in the list
in your case:
SelectedValueBinding="{Binding EarlyOTRounding, UpdateSourceTrigger=PropertyChanged}"
Please help me figure out how to bind custom class collection to datagrid combobox.
My custom class is
class Test: INotifyPropertyChanged
{
public String Name { get; set; }
public UserAvailableValue SelectedAvailableValue { get; set; }
public ObservableCollection<UserAvailableValue> AvailableValues { get; set; }
public ObservableCollection<String> DefaultValues { get; set; }
public String SelectedValue { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class UserAvailableValue
{
public Object Value { get; set; }
public Object Label { get; set; }
}
From code behind, i am setting DataGrid Datacontext i.g.
ObservableCollection<Test> UIParams = new ObservableCollection<Test>();
// code to fill UIParams collection
dgReportparameters.DataContext = UIParams;
//XAML Code
<DataGrid Name="dgReportparameters" ItemsSource="{Binding}"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
<DataGridComboBoxColumn Header="Available Values" SelectedItemBinding=
"{Binding SelectedAvailableValue, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="Label">
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding Path=AvailableValues,
RelativeSource={RelativeSource AncestorType=Window}}" />
</Style>
</DataGridComboBoxColumn.ElementStyle>
</DataGridComboBoxColumn>
<DataGridTextColumn Header="Default Values" Binding="{Binding SelectedValue}"/>
<DataGridCheckBoxColumn Header="Nullable" Binding="{Binding IsNullable}"/>
</DataGrid.Columns>
</DataGrid>
Except DataGridComboBoxColumn other columns are showing correct values.DataGridComboBoxColumn is showing blank column. UIParams collection has multiple parameters while each parameter has name and Available values and one default value. I want to show paramters in datagrid and let user select one/multiple values from Available column combobox.
Each parameter has its own set of available values. Most of the example i found have Common collection in dropdown but in my case each row of datagrid has different available values.
Please help me to have combobox in datagrid.
Thanks in advance.
Your Combobox Style is wrong. The ItemsSource is set to Window whereas from your Model, you don't need to set the AncestorType.
Just {Binding} is enough. Actually, you don't need the ItemsSource Setter as you are just defining the style of the Combobox. You need it only if you are modifying the ItemsSource to something else. In your case, its not.
Edit:
Take look on this example: Binding ItemsSource of a ComboBoxColumn in WPF DataGrid
I don't understand your requirement of having AvailableValues collection in each Data Item which you have have it in Top Level like below.
public class MyClass
{
public List<Test> Items{get;set;}
public List<AvailableValue> AvailableValues { get;set;}
}
I also notices that you have implemented INotifyPropertyChanged interface and not raising the change event on the each property set.
I would suggest you to learn some basic of WPF and INotifyPropertyChanged interface before you start working on them.
This helped me.
<DataGridComboBoxColumn Header="Available Values" SelectedItemBinding=
"{Binding SelectedAvailableValue, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="Label">
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding Path=AvailableValues}" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
I have a ListBox with a GroupStyle that contains another ListBox.
Now I want to filter the Items of the nested ListBox depending on the group name of the parent ListBox.
In the code below I tried to chain the GroupItem.Name.Name property to the GroupName property of the ViewModel of the nested ListBox, but this didn't work out so well.
Essentially the GroupNameIn Property is filled by the GroupItems' name(the TextBlock Text) and then sets the GroupNameOut Property to the same value in the PropertyChangedCallback. But the problem is that the GroupName Property of the NestedItemsViewModel to which GroupNameOut is bound to doesn't update.
Are there some mistakes in my approach or is there even a simpler/better way to achieve this behavior?
I would be very grateful if someone could point me in the right direction.
GroupStyle of the parent ListBox:
<Style x:Key="MyListBoxGroupStyle" TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<StackPanel Name="container" Width="Auto" Orientation="Vertical">
<TextBlock Name="groupNameTextBlock" Text="{Binding Path=Name.Name}"/>
<ItemsPresenter/>
<MyNestedListBox
DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBox}}, Path=NestedItemsDataContext}"
ItemsSource="{Binding NestedItems}"
GroupNameIn="{Binding ElementName=groupNameTextBlock, Path=Text}"
GroupNameOut="{Binding Path=GroupName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The nested ListBox:
public class MyNestedListBox : ListBox
{
static MyNestedListBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyNestedListBox), new FrameworkPropertyMetadata(typeof(MyNestedListBox)));
}
public string GroupNameIn
{
get { return (string)GetValue(GroupNameInProperty); }
set { SetValue(GroupNameInProperty, value); }
}
public string GroupNameOut
{
get { return (string)GetValue(GroupNameOutProperty); }
set { SetValue(GroupNameOutProperty, value); }
}
// DepenencyProperties
public static readonly DependencyProperty GroupNameInProperty =
DependencyProperty.Register("GroupNameIn", typeof(string), typeof(MyNestedListBox), new UIPropertyMetadata(null) { PropertyChangedCallback = (obj, target) =>
{
obj.SetValue(GroupNameOutProperty, target.NewValue);
}
});
public static readonly DependencyProperty GroupNameOutProperty =
DependencyProperty.Register("GroupNameOut", typeof(string), typeof(MyNestedListBox), new UIPropertyMetadata(null));
}
ViewModel bound to the nested ListBox:
public class NestedItemsViewModel : ViewModelBase
{
private string _groupName;
public ObservableCollection<NestedItem> NestedItems { get; set; }
public string GroupName
{
get
{
return _groupName;
}
set
{
_groupName = value;
OnPropertyChanged(() => GroupName);
}
}
public NestedItemsViewModel()
{
NestedItems = new ObservableCollection<NestedItem>();
}
}
In my WPF application I am using the MVVM pattern. My view has a treeview which I bind an observableCollection of objects as defined below. What I want to do is to change the colour of a tree item name when the bound object sets it’s dirty property to true. I can get it to set the colour when I first populate the tree but then it doesn’t reflect the changes when the property changes between false and true.
public class HierarchicalItem
{
private readonly ObservableCollection<HierarchicalItem> _children = new ObservableCollection<HierarchicalItem>();
public ViewModelBase ViewModel { get; set; }
public string Name
{
get { return ViewModel.ViewModelName; }
}
public ICollection<HierarchicalItem> Children
{
get { return _children; }
}
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
if (_isSelected)
EventSystem.Publish(new SelectedViewModelMessage { SelectedViewModel = ViewModel });
}
}
public bool IsDirty
{
get { return ViewModel.IsDirty; }
}
}
This is the treeview xaml:
<TreeView Grid.Row="0" Grid.Column="0" ItemsSource="{Binding Path=Views}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True"/>
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=OneWayToSource}" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:HierarchicalItem}" ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding IsDirty}" Value="True">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
Here is the collection that gets bound to the tree:
private readonly ObservableCollection<HierarchicalItem> _views = new ObservableCollection<HierarchicalItem>();
public ObservableCollection<HierarchicalItem> Views
{
get { return _views; }
}
The ViewModels that are referenced in the HierarchicalItem collection all derive from a base class that exposes the “IsDirty” property. This is definantly changing state so I’m not sure if I’ve made a coding mistake or if what I want to achieve can’t be done this way. The classes all use the “INotifyPropertyChanged” interface. Here is the “IsDirty” property in from the ViewModel base class:
public class ViewModelBase : ValidatableModel
{
#region Properties
private bool _isDirty;
public bool IsDirty
{
get { return _isDirty; }
protected set
{
_isDirty = value;
OnPropertyChanged("IsDirty");
}
}
.
.
.
Etc
It's because your HierarchicalItem (the one you are having issues with) does not use a full INPC approach for its IsDirty property. The viewmodel does, but that is not enough, as the DataTemplate will be using the IsDirty property of the HierarchicalItem, so that needs to be full INPC property too
Changed that to this and it should be ok.
private bool _isDirty;
public bool IsDirty
{
get { return _isDirty; }
protected set
{
_isDirty = value;
OnPropertyChanged("IsDirty");
}
}
Though for your use case you will need to figure out some way to fire that. Or another thing you could try would be to change the binding in HierarchicalItem DataTemplate to this
<DataTrigger Binding="{Binding ViewModel.IsDirty}" Value="True">