I have trouble when I try to select some Item in DataGrid programmatically. Without using MVVM pattern all is OK. Look at XAML:
<DataGrid
Name="_dataGrid"
AutoGenerateColumns="False"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}"/>
<DataGridTextColumn Binding="{Binding SecondName}"/>
</DataGrid.Columns>
</DataGrid>
Code behind:
public class GridItem
{
public String Name { get; set; }
public String SecondName { get; set; }
}
public partial class Window1 : Window
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(String propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private GridItem _selectedItem;
public GridItem SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
RaisePropertyChanged("SelectedItem");
}
}
public Window1()
{
InitializeComponent();
DataContext = this;
_dataGrid.Loaded += DataGridLoaded;
Init1();
}
void DataGridLoaded(object sender, RoutedEventArgs e)
{
Int32 rowIndex = 2;
var selItem = _dataGrid.Items[rowIndex];
SelectedItem = (GridItem)selItem; <-------- Bad
//_dataGrid.SelectedItem = selItem; <-------- Good
}
private void Init1()
{
var source = new List<GridItem>();
source.Add(new GridItem
{
Name = "pavlik",
SecondName = "bobr"
});
source.Add(new GridItem
{
Name = "alex",
SecondName = "ugr"
});
source.Add(new GridItem
{
Name = "den",
SecondName = "ivanov"
});
source.Add(new GridItem
{
Name = "dima",
SecondName = "klim"
});
_dataGrid.ItemsSource = source;
}
}
So, when I select Item like that
_dataGrid.SelectedItem = selItem; // Good
Item is selected and highlighted properly.
But when I try to select and highlight Item via Model property, Item is not highlighted!
SelectedItem = (GridItem)selItem; // Bad
What is the reason? Any idea?
you need to inherit your window or any viewmodel class from INotifyPropertyChanged otherwise it will not Notifies clients that a property value has changed.
for your case it could be like
public partial class Window1 : INotifyPropertyChanged
{
// Class code goes here;
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(String propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
I do this a lot, DataGrid to ViewModel, and I never have to do
SelectedItem = (GridItem)selItem;
i.e cast the item.
As you have SelectedItem on the DataGrid XAML, it knows the type you are binding too.
However, I normally use ItemSource on the gridview as well.
e.g. ItemSource = "{Bind the collection of GridItem}"
so in ViewModel, I would have Observablecollection or List as a property.
My WPF knowledge is only like 2 weeks old so I could be totally be wrong, but I think the cast and the Itemsource is where you need to look.
I could give you an example if you like.
Cheers
Related
I have next model:
public class MyModel
{
public ObservableCollection<MyObject> MyList {get; set;}
}
public class MyObject
{
MyObservableDictionary MyDictionary {get; set;}
}
public class MyObservableDictionary : ObservableCollection<EnymValue>
{
}
public class EnymValue : INotifyPropertyChanged
{
private MyEnum key;
private string value;
public MyEnum Key
{
get
{
return this.key;
}
set
{
this.key = value;
NotifyPropertyChanged("Key");
}
}
public string Value
{
get
{
return this.value;
}
set
{
this.value = value;
NotifyPropertyChanged("Value");
}
}
public LanguageValue(MyEnum key, string value)
{
this.Key = key;
this.Value = value;
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([System.Runtime.CompilerServices.CallerMemberName]string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public enum MyEnum
{
}
And on View I have a ListBox:
<ListBox x:Name="MyList" SelectionMode="Single" ItemsSource="{Binding Path=MyList, Mode=OneWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=MyDictionary, Mode=OneWay, Converter={StaticResource myEnumToTextConverter}}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
(myEnumToTextConverter converter is just selects first element from collection and return it's value, or some specified constant if collection is null or empty)
I want my Model's list box to be updated on view, when any EnymValue values are changed.
Is it possible somehow to implement this?
Currently the view is not updated when Value changed.
I've tried to inherit EnymValue from INotifyPropertyChanged, but this didn't helped. Looks like PropertyChanged == null on EnymValue.NotifyPropertyChanged when property updated.
ObservableCollection is able to notify UI about changes when collection itself is changed(elemends are added or deleted). But ObservableCollection is not aware of changes that are happening when you modify one of it's items. To solve the problem you may subscribe to CollectionChange event of observable collection, and when new item is added, subscribe to new items's PropertyChanged. When PropertyChanged event is raised, you can trigger notification on your list OnPropertyChanged(()=>MyItems); You should be careful implementing this solution and remember to unsubscribe from the event's to avoid memory leaks.
An example of what I mean you can see in this answer.
Your MyDictionary should force a refresh. Easiest way is to re-assign its old value, and implement INPC in MyObject like below :
public class MyObject: INotifyPropertyChanged
{
MyObservableDictionary _myDictionary;
public MyObservableDictionary MyDictionary {
get
{
return _myDictionary;
}
set
{
_myDictionary = value;
OnPropertyChanged("MyDictionary");
}
}
public MyObject()
{
MyDictionary = new MyObservableDictionary();
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string prop)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
Sample code to change Value :
private void Button_Click(object sender, RoutedEventArgs e)
{
// vm is ViewModel instance, vm is DataContext set for Window
var old = vm.MyList[0].MyDictionary;
vm.MyList[0].MyDictionary[0].Value = "aaaa";
vm.MyList[0].MyDictionary = old;
}
I tested this, and it displays changed value as "aaaa".
I have scenario in Combobox, We want to change the current SelectedItem of Combobox when certain value is selected.
For Instance: We have designation Combobox having values: CEO, Manager, Dev, QA..
When CEO is selected we would like to change it to Manager value.
SelectedValue is bound to property in ViewModel.
Aha! I thought it was a silly question and could be answered without spending much effort :P. But, it thought me a lesson that WPF is not working the way what I think as always.
Here is the sample working solution.
MainWindow.xaml
<Grid>
<ComboBox Width="100" Height="50" ItemsSource="{Binding ComboList}" SelectedValue="{Binding Selected, Mode=TwoWay, IsAsync=True}"/>
</Grid>
MainWindow.xaml.cs
public MainWindow()
{
InitializeComponent();
ObservableCollection<string> lst = new ObservableCollection<string>();
lst.Add("CEO");
lst.Add("Tester");
lst.Add("president");
lst.Add("Developer");
lst.Add("Manager");
MainWindowViewModel vm = new MainWindowViewModel() { ComboList = lst, Selected = "Employee" };
this.DataContext = vm;
}
MainWindowViewModel.cs
public class MainWindowViewModel : INotifiPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
public ObservableCollection<string> ComboList { get; set; }
private string selected;
public string Selected
{
get { return selected; }
set
{
selected = value == "CEO" ? "Manager" : value;
OnPropertyChanged("Selected");
}
}
}
The Key is IsAsync=True which did the trick here. Thanks to Martin Harris for his answer
I have a combo box category that is binded to an ObservableCollection Categories based on tbl_Category with two properties CategoryName and CategoryDescription. Now I want to add the SelectedValue of the ComboBox to product table property Prod_Category
In my constructor :
cb.DataContext = Categories;
this.DataContext = new tbl_Product();
Combo box xaml :
<Combobox x:Name="cb" ItemSource="{Binding Categories}" DisplayMemberPath="CategoryName" SelectedValuePath="CategoryName" SelectedValue="{Binding Prod_Category,Mode=TwoWay}"/>
In my save product event :
tbl_Product prod = (tbl_Product)this.DataContext;
DataOperations.AddProduct(prod);
I get Prod_Category to null even after doing all this.
You should use SelectedItem instead of SelectedValue, refer to this.
besides that what you are willing to do wasn't so clear, I've tried to implement what you asked for based on my understanding
public partial class MainWindow : Window,INotifyPropertyChanged
{
private ObservableCollection<Category> _categories;
public ObservableCollection<Category> Categories
{
get
{
return _categories;
}
set
{
if (_categories == value)
{
return;
}
_categories = value;
OnPropertyChanged();
}
}
private Category _prod_Category ;
public Category Prod_Category
{
get
{
return _prod_Category;
}
set
{
if (_prod_Category == value)
{
return;
}
_prod_Category = value;
OnPropertyChanged();
}
}
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
Categories=new ObservableCollection<Category>()
{
new Category()
{
CategoryName = "Name1",
CategoryDescription = "Desc1"
},new Category()
{
CategoryName = "Name2",
CategoryDescription = "Desc2"
}
};
}
public void SaveButton_Click(object sender, RoutedEventArgs routedEventArgs)
{
if (Prod_Category!=null)
{
//add it to whatever you want
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Category
{
public String CategoryName { get; set; }
public String CategoryDescription { get; set; }
}
and the xaml
<StackPanel>
<ComboBox x:Name="cb" ItemsSource="{Binding Categories}" DisplayMemberPath="CategoryName" SelectedValuePath="CategoryName" SelectedItem="{Binding Prod_Category,Mode=TwoWay}"/>
<Button Content="Save" Click="SaveButton_Click"></Button>
</StackPanel>
you should consider implementing the INotifyPropertyChanged interface to notify the UI if any changes occurs in one of the binded properties
In addition to #samthedev 's provided answer, I would also recommend you change your assignment of DataContext from:
tbl_Product prod = (tbl_Product)this.DataContext;
DataOperations.AddProduct(prod);
to
tbl_Product prod = this.DataContext as tbl_Product;
if (tbl_Product != null)
{
DataOperations.AddProduct(prod);
}
This prevents any change of DataContext being accidentally bound to a different object and causing an unhandled exception due to the fact that DataContext does have tbl_Product as its base-type which can happen more often than you realise in WPF due to DataContext inheritance when someone changes something at a higher level.
I have simplified this problem down as much as I can. Basically I am overriding the "null" value of a combobox. So that if the item selected is deleted, it reverts back to "(null)". Unfortunately the behaviour of this is wrong, I hit delete, the ObservableCollection item is removed, thus the property binding is updated and it returns the "(null)" item as expected. But the combobox appearance shows blank. Yet the value its bound to is correct... this problem can be reproduced with the code below.
To reproduce this problem you select an item, and hit remove. Notice at this point the following line is called (when you remove the selected item). So its a good place to breakpoint.
if (m_Selected == null)
{
return Items[0]; //items 0 is ItemNull
}
Also notice that I have attmpted to fix it by Forcing a property update on the DisplayMemberPath. This did not work.
MainWindow.xaml
<Window x:Class="WPFCodeDump.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<ComboBox ItemsSource="{Binding Items}" SelectedItem="{Binding Selected, Mode=TwoWay}" DisplayMemberPath="Name"></ComboBox>
<Button Click="ButtonBase_OnClick">Remove Selected</Button>
</StackPanel>
</Window>
MainWindowViewModel.cs
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;
namespace WPFCodeDump
{
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
//Item class
public class Item : ViewModelBase
{
public Item(string name)
{
m_Name = name;
}
public string Name
{
get { return m_Name; }
}
private string m_Name;
public void ForcePropertyUpdate()
{
OnPropertyChanged("Name");
}
}
//Item class
public class ItemNull : Item
{
public ItemNull()
: base("(null)")
{
}
}
class MainWindowViewModel : ViewModelBase
{
public MainWindowViewModel()
{
m_Items.Add(new ItemNull());
for (int i = 0; i < 10; i++)
{
m_Items.Add(new Item("TestItem" + i));
}
Selected = null;
}
//Remove selected command
public void RemoveSelected()
{
Items.Remove(Selected);
}
//The item list
private ObservableCollection<Item> m_Items = new ObservableCollection<Item>();
public ObservableCollection<Item> Items
{
get { return m_Items; }
}
//Selected item
private Item m_Selected;
public Item Selected
{
get
{
if (m_Selected == null)
{
return Items[0]; //items 0 is ItemNull
}
return m_Selected;
}
set
{
m_Selected = value;
OnPropertyChanged();
if(m_Selected!=null) m_Selected.ForcePropertyUpdate();
}
}
}
}
MainWindow.xaml.cs
using System.Windows;
namespace WPFCodeDump
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
((MainWindowViewModel) DataContext).RemoveSelected();
}
}
}
Result:
A nice binding issue you found there. But as always, it's our fault, not theirs :)
The issue(s) is(are), using DisplayMemberPath with SelectedItem.
The DisplayMemberPath doesn't give a f*** about the changed SelectedItem.
What you have to do, to resolve this issue, are two things:
First, in the RemoveSelected method, set the Selected property to null (to force an update on the binding):
public void RemoveSelected()
{
Items.Remove(Selected);
Selected = null;
}
Then, in the XAML-definition, change the bound property:
<ComboBox ItemsSource="{Binding Items}"
SelectedValue="{Binding Selected, Mode=TwoWay}"
DisplayMemberPath="Name"/>
Binding the SelectedValue property will correctly update the displayed text in the ComboBox.
I have dynamic listbox contains textbox to display list items and so I can edit listbox item. My application setting file contains string collection which I want to bind for that listbox. I also want to update that setting files on every change of listbox item, I created class which implements INotifyProprtyChanged. I have converted string collection from settings file into observable collection of custom type which has string property. I bind textbox to property of that custom class and update source property on property change. I want to update observable collection as well. and that updates my app setting file as well. Please help me on this. Any help would be really appreciated. My code:
public class WindowViewModel : INotifyPropertyChanged
{
private ObservableCollection<UrlModel> customcollection;
public ObservableCollection<UrlModel> CustomCollection
{
get { return customcollection; }
set
{
customcollection = value;
NotifyPropertyChanged("CustomCollection");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
public WindowViewModel()
{
List<string> customlist = Properties.Settings.Default.CustomList.Cast<string>().ToList();
List<UrlModel> urllist = new List<UrlModel>();
urllist = customlist.Select(item => new UrlModel() { urlString = item }).ToList();
CustomCollection = new ObservableCollection<UrlModel>(urllist);
}
}
public class UrlModel : INotifyPropertyChanged
{
private string url;
public string urlString
{
get { return url; }
set
{
url = value;
NotifyPropertyChanged("urlString");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ViewModel = new WindowViewModel();
ListTwo.ItemsSource = ViewModel.CustomCollection;
}
private WindowViewModel viewModel;
public WindowViewModel ViewModel
{
get { return viewModel; }
set{
viewModel = value;
DataContext = value;
}
}
}
}
Add property changed event handler for each url when inserting into ObservableCollection
public WindowViewModel()
{
List<string> customlist = Properties.Settings.Default.CustomList.Cast<string>().ToList();
List<UrlModel> urllist = new List<UrlModel>();
urllist = customlist.Select(item => new UrlModel() { urlString = item }).ToList();
CustomCollection = new ObservableCollection<UrlModel>(urllist);
foreach(var model in CustomCollection)
{
model.PropertyChaged += SettingsUpdater; //Settings update fucntion
}
}
A naive implementation of SettingsUpdater would just update the whole list of urls in settings whenever one of them changes.
I believe you are using data template to make your listbox editable. If that is the case, while binding the text, include Text="{Binding urlString,UpdateSourceTrigger=PropertyChanged}" in the Xaml code.
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Name="EditableText" Text="{Binding urlString,UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</ListBox.ItemTemplate>