I have this Order class and I am using MVVM:
namespace drinksMVVM.Models
{
public class Order : INotifyPropertyChanged
{
private Item _OrderItem;
private ObservableCollection<Item> _Supplements;
private double _OrderPrice;
public Item OrderItem
{
get
{
return _OrderItem;
}
set
{
_OrderItem = value;
OnPropertyChanged("OrderItem");
_OrderPrice += _OrderItem.Price;
}
}
public ObservableCollection<Item> Supplements
{
get
{
return _Supplements;
}
set
{
_Supplements = value;
OnPropertyChanged("Supplements");
foreach(var supplement in _Supplements)
{
_OrderPrice += supplement.Price;
}
}
}
public double OrderPrice
{
get
{
return _OrderPrice;
}
set
{
_OrderPrice = value;
OnPropertyChanged("Price");
}
}
public Order()
{
}
public Order(Item item, ObservableCollection<Item> items)
{
_OrderItem = item;
_Supplements = items;
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
}
It contains object from type Item(properties Name and Price) and ObservableCollection of type Item. The Order object certainly contains one Item which is a single food/drink as order, but can contain Item and ObservableCollection, which is a food/drink with additional supplements.
Simple example:
I have order of Coffee with Sugar and Cream and I want to display them something like that:
Coffee (OrderItem)
Sugar+Cream (Supplements)
1,50 (OrderPrice)
The layout does not matter in this case
My question here is how to display the data in my view. I have two ideas which don't know how to continue.
DataTemplate with textblocks, each textblock is bind to a property of the Order class, but don't know how to display every single item name from the ObservableCollection.
<DataTemplate x:Key="OrderTemplate">
<StackPanel Orientation="Vertical">
<TextBlock x:Name="OrderName" Text="{Binding OrderItem.Name}"/>
<TextBlock x:Name="OrderSupplements"
Text="{Binding Supplements[0].Name}"/> ///Can't solve this
binding
<TextBlock x:Name="OrderPrce" Text="{Binding OrderPrice}"/>
</StackPanel>
</DataTemplate>
2.Override the ToString() method of the Order class in order to display all of the data in one single textblock, but also can't find a solution how to display all supplements names.
public override string ToString()
{
return OrderItem.Name + " " +/*every single supplement name*/+" "
+OrderPrice.ToString();
}
And
<DataTemplate x:Key="OrderTemplate1">
<StackPanel Orientation="Vertical">
<TextBlock x:Name="test" Text="{Binding}"/>
</StackPanel>
</DataTemplate>
As far as option #2 goes...
public override string ToString()
{
string orderStr = string.Empty;
orderStr += _OrderItem.Name + " ";
foreach(Item supplement in Supplements)
{
orderStr += supplement.Name + " ";
}
orderStr += OrderPrice.ToString();
return orderStr;
}
I managed to solve option #1 with IValueConverter
[ValueConversion(typeof(List<Item>),typeof(string))]
public class MyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
List<Item> items = (List<Item>)value;
string supplementsString = string.Empty;
if (items.Count >= 1)
{
foreach (var item in items)
{
supplementsString += item.Name + " " + item.Price.ToString() + " " + Environment.NewLine;
}
}
return supplementsString;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
And XAML part
<converters:MyConverter x:Key="MyConverterTest"/>
<TextBlock x:Name="OrderSupplements"
Text="{Binding Supplements, Converter={StaticResource MyConverter}}"/>
Related
My target is to create a combobox which displays any list of strings when opened (so the standard behavior), but when the user selects one of the strings, it gets added at the top of the list under a "Recently used" separator.
Essentially, I want a control that behaves exactly like the one to select fonts in MS Word:
My start was to create a custom control with an additional Dependency property which holds the recently selected items. This list gets updated when the user selects an item from the list. I don't want to modify the original list of items, since I aim to get a reusable control where the user does not have to manage the most recent items themselves.
private static readonly DependencyPropertyKey LastSelectedItemsPropertyKey =
DependencyProperty.RegisterReadOnly(
"LastSelectedItems",
typeof (Dictionary<string, int>),
typeof (MemoryCombobox),
new FrameworkPropertyMetadata(default(ObservableCollection<string>), FrameworkPropertyMetadataOptions.None));
public static readonly DependencyProperty LastSelectedItemsProperty = LastSelectedItemsPropertyKey.DependencyProperty;
My question now is: how can I display all items (labels and both lists) in a single dropdown of the combobox, like this:
---------------------
Label: Recently Selected
---------------------
<All items from the 'LastSelectedItems' DependencyProperty>
---------------------
Label: All Items
---------------------
<All items from the 'ItemsSource' property of the combobox
---------------------
I don't want to use grouping for this, since the items would not be repeated in the "all items" list below the recently used ones, like the user would expect them to be.
Have you tried something along these lines. It uses grouping, but does it in a special way, so that mru-items are not removed from the total list/group:
XAML:
<ComboBox Name="MyCombo" SelectionChanged="MyCombo_SelectionChanged" VerticalAlignment="Top">
<ComboBox.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" Background="DarkGray" Foreground="White" FontWeight="Bold" />
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ComboBox.GroupStyle>
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" Margin="0,0,5,0" />
<TextBlock Text="{Binding Value}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Code Behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
m_preventFeedback = true;
ItemsList = new ObservableCollection<VMItem>
{
new VMItem(new Item("John", 1234), 2),
new VMItem(new Item("Peter", 2345), 2),
new VMItem(new Item("Michael", 3456), 2),
};
ListCollectionView view = new ListCollectionView(ItemsList);
view.GroupDescriptions.Add(new PropertyGroupDescription("CategoryId", new ItemGroupValueConverter()));
MyCombo.ItemsSource = view;
m_preventFeedback = false;
}
private ObservableCollection<VMItem> ItemsList = new ObservableCollection<VMItem>();
bool m_preventFeedback = false;
private void MyCombo_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (m_preventFeedback) return;
if (MyCombo.SelectedItem is VMItem item)
{
m_preventFeedback = true;
VMItem mru = ItemsList.FirstOrDefault(i => i.Name == item.Name && i.CategoryId == 1) ?? new VMItem(item.Item, 1);
ItemsList.Remove(mru);
ItemsList.Insert(0, mru);
MyCombo.SelectedItem = mru;
m_preventFeedback = false;
}
}
}
public class ItemGroupValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
switch ((int)value)
{
case 1: return "Last Used";
case 2: return "Available Items";
}
return "N/A";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
public class VMItem : INotifyPropertyChanged
{
private Item m_item;
public VMItem(Item item, int categoryId)
{
m_item = item;
m_categoryId = categoryId;
}
public string Name
{
get { return m_item.Name; }
set
{
m_item.Name = value;
OnPropertyChanged("Name");
}
}
public int Value
{
get { return m_item.Value; }
set
{
m_item.Value = value;
OnPropertyChanged("Value");
}
}
private int m_categoryId;
public int CategoryId
{
get { return m_categoryId; }
set
{
m_categoryId = value;
OnPropertyChanged("CategoryId");
}
}
public Item Item => m_item;
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string property)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
}
public class Item
{
public Item(string name, int value)
{
Name = name;
Value = value;
}
public string Name { get; set; }
public int Value { get; set; }
}
GitHub Link: https://github.com/babakin34/wpf_test/tree/master/WpfApp1
I have the following classes:
VIEWMODELS:
public class PersonVM : BindableBase
{
public int ID { get; set; }
private string _lastName;
public string LastName
{
get { return _lastName; }
set { SetProperty(ref _lastName, value); }
}
}
public class MainVM : BindableBase
{
public ObservableCollection<PersonVM> People { get; set; }
private PersonVM _selectedPerson;
public PersonVM SelectedPerson
{
get { return _selectedPerson; }
set { SetProperty(ref _selectedPerson, value); }
}
public MainVM()
{
People = new ObservableCollection<PersonVM>()
{
new PersonVM()
{
ID = 1,
LastName = "AA"
},
new PersonVM()
{
ID = 2,
LastName = "BB"
},
};
SelectedPerson = People.First();
}
}
VIEW:
<Grid>
<StackPanel>
<ComboBox ItemsSource="{Binding People}"
SelectedItem="{Binding SelectedPerson}"
DisplayMemberPath="LastName"
Margin="0,5,0,25"/>
<DataGrid ItemsSource="{Binding People}"/>
</StackPanel>
</Grid>
How can I achieve that the "MainVM.SelectedPerson" from ComboBox is notified when user selects the empty element, which is caused by the Datagrid's default last entry?
PS: I am using Prism, but the problem is not Prism related. You can replace BindableBase by INotifyPropertyChanged.
Edit:
The real issue here is a bit different than i thought at first. When selecting the insert row, you see in the output window that WPF is unable to cast a "NamedObject" into a "PersonVM". The Datagrid creates a NamedObject for the insert row to work with, but the binding simply does not work since it's of the wrong type.
The clean and easy solution is to create a converter to check if the object is of type PersonVM, otherwise return null (and not the NamedObject instance).
public class MyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is PersonVM)
return value;
return null;
}
}
And in the xaml
<DataGrid x:Name="DataGrid"
ItemsSource="{Binding People}"
SelectedCellsChanged="DataGrid_OnSelectedCellsChanged"
SelectedItem="{Binding SelectedPerson,
Converter={StaticResource myConverter}}"
Old and dirty
Not the nicest way, but if you don't mind using the viewmodel (or abstracting it via an interface) in the codebehind you can use the "SelectionChanged" event to set the SelectedPerson in the ViewModel to null if any of the selected items are not of the type you need.
private void Selector_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
bool invalidStuffSelected = false;
//throw new System.NotImplementedException();
foreach (var obj in DataGrid.SelectedItems)
{
if (!(obj is PersonVM))
invalidStuffSelected = true;
}
MainVM vm = (MainVM) this.DataContext;
if (invalidStuffSelected)
vm.SelectedPerson = null;
}
In your example the selectionmode "single" would make more sense since the combobox can only show one selected value.
I have a form, which is binded to the model. There is one standard, basic model and few children models (with additional fields).
Above the model's controls there is a radio buttons group and upon selecting one of them the forementioned additional fields appear (in this case the sentence field).
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void sentencedPersonRadioButton_Checked(object sender, RoutedEventArgs e)
{
sentenceTextBox.Visibility = Visibility.Visible;
DataContext = new SentencedPerson();
}
private void personRadioButton_Checked(object sender, RoutedEventArgs e)
{
sentenceTextBox.Visibility = Visibility.Hidden;
DataContext = new Person();
}
}
Lets say there is a Person and SentencedPerson:
public class Person: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private String name;
public String Name
{
get
{
return name;
}
set
{
if (value == name)
{
return;
}
name = value;
OnPropertyChanged("Name");
}
}
}
public class SentencedPerson : Person
{
private String sentence;
public String Sentence
{
get
{
return sentence;
}
set
{
if (value == sentence)
{
return;
}
sentence = value;
OnPropertyChanged("Sentence");
}
}
}
What is the proper way to design such a connections? Adding new 'checked' event handlers feels so cumbersome... I heard about MVVM pattern in which there would be some kind of PersonContext with Person and SentencedPerson props inside. But it does not change the need of 'checked' events.
Also know there is a problem because the values of common fields are after setting the new DataContext.
This is a quite broad question but I will give you some pointers.
MVVM is the recommended design pattern to use when building XAML based applications.
You could create a view model class with a "CurrentSelectedContent" property of type object or Person and an enum property that you bind the RadioButton to.
Please refer to the following link for more information and an example of how to bind a RadioButton to an enum source property using MVVM:
How to bind RadioButtons to an enum?
Once you have done this you could set the value of the "CurrentSelectedContent" property based on the radio button selection in the setter of the enum source property in the view model:
private MyLovelyEnum _enum;
public MyLovelyEnum VeryLovelyEnum
{
get
{
return _enum;
}
set
{
_enum = value;
switch (value)
{
case MyLovelyEnum.Person:
CurrentSelectedContent = new Person();
break;
//...
}
OnPropertyChanged("VeryLovelyEnum");
}
}
Make sure that the "CurrentSelectedContent" property raises the PropertyChanged event and that the view model class implements the INotifyPropertyChanged interface.
In the view you could then use a ContentControl and bind its Content property to the "CurrentSelectedContent" property:
<ContentControl Content="{Binding Content}">
<ContentControl.ContentTemplate>
<DataTemplate>
<TextBox Text="{Binding Name}" />
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
Also make sure that you set the DataContext of the view to an instance of your view model:
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
This is the rough idea on how to do this using the MVVM pattern. Instead of handling events in the code-behind of the view you bind to source properties and instead of setting the DataContext property of specific UI elements explicitly you bind the Content property of a ContentControl to an object that you create in the view model class.
Hope that helps.
You just need One model:
public class Person : INotifyPropertyChanged
{
string _name;
public string Name { get { return _name; } set { _name = value; RaisePropertyChanged("Name"); } }
bool _isSentenced;
public bool IsSentenced { get { return _isSentenced; } set { _isSentenced = value; RaisePropertyChanged("IsSentenced"); } }
string _sentence;
public string Sentence { get { return _sentence; } set { _sentence = value; RaisePropertyChanged("Sentence"); } }
public event PropertyChangedEventHandler PropertyChanged;
void RaisePropertyChanged(string propname)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propname));
}
}
Use IsSentenced and bind the RadioButton to it. Also, check the Visibility of the TextBox displaying the Sentence string to the IsChecked property of the RadioButton, using a Visibility to Bool converter. Here is a simple example:
<Window.Resources>
<local:VisibilityToBoolConverter x:Key="VisibilityToBoolConverter"/>
</Window.Resources>
<ListBox DataContext="{Binding}" ItemsSource="{Binding Persons}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBox Text="{Binding Name}" />
<RadioButton Content="Is sentenced to death" IsChecked="{Binding IsSentenced}" />
<DockPanel Visibility="{Binding IsSentenced , Converter={StaticResource VisibilityToBoolConverter}}">
<Label Content="Sentence: "/>
<TextBlock Text="{Binding Sentence}" />
</DockPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
in which
public class VisibilityToBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if ((bool)value == true)
return Visibility.Visible;
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if ((Visibility)value == Visibility.Visible)
return true;
return false;
}
}
and the ViewModel is:
public class PersonViewModel : INotifyPropertyChanged
{
public PersonViewModel()
{
Person m1 = new Person() { Name = "person 1", IsSentenced = false, Sentence = "S S S" };
Person m2 = new Person() { Name = "person 2", IsSentenced = false, Sentence = "B B B" };
Person m3 = new Person() { Name = "person 3", IsSentenced = true, Sentence = "F F F" };
_persons = new ObservableCollection<Person>() { m1, m2, m3 };
}
ObservableCollection<Person> _persons;
public ObservableCollection<Person> Persons { get { return _persons; } set { _persons = value; RaisePropertyChanged("Persons"); } }
public event PropertyChangedEventHandler PropertyChanged;
void RaisePropertyChanged(string propname)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propname));
}
}
your main window should set the DataContext:
public MainWindow()
{
PersonViewModel mv = new PersonViewModel();
this.DataContext = mv;
InitializeComponent();
}
Edit
If there are many states for a person, ComboBox is a more natural choice. You should have an Enum that describes the states:
public enum MyTypes
{
None,
IsA,
IsB,
IsC
}
and the Person should have a peroperty that shows the state:
public class Person : INotifyPropertyChanged
{
MyTypes _thetype;
public MyTypes TheType { get { return _thetype; } set { _thetype = value; RaisePropertyChanged("TheType"); } }
string _name;
public string Name { get { return _name; } set { _name = value; RaisePropertyChanged("Name"); } }
public event PropertyChangedEventHandler PropertyChanged;
void RaisePropertyChanged(string propname)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propname));
}
}
Since you need to bind the ItemsSource of your ComboBox to a list of states, one possibility is to adjust the ViewModel to have such a list:
public class PersonViewModel : INotifyPropertyChanged
{
public PersonViewModel()
{
Person m0 = new Person() { Name = "person 1", TheType = MyTypes.None };
Person m1 = new Person() { Name = "person 1", TheType = MyTypes.IsA };
Person m2 = new Person() { Name = "person 2", TheType = MyTypes.IsB };
Person m3 = new Person() { Name = "person 3", TheType = MyTypes.IsC };
_persons = new ObservableCollection<Person>() { m0, m1, m2, m3 };
_types = Enum.GetNames(typeof(MyTypes)).ToList();
}
List<string> _types;
public List<string> Types { get { return _types; } }
ObservableCollection<Person> _persons;
public ObservableCollection<Person> Persons { get { return _persons; } set { _persons = value; RaisePropertyChanged("Persons"); } }
public event PropertyChangedEventHandler PropertyChanged;
void RaisePropertyChanged(string propname)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propname));
}
}
and at last, the View:
<Window.Resources>
<local:EnumToSentenceConverterx:Key="EnumToSentenceConverter"/>
<local:NoneToCollapsedConverter x:Key="NoneToCollapsedConverter"/>
<local:EnumToStringConverter x:Key="EnumToStringConverter"/>
</Window.Resources>
<ListBox Name="lb" DataContext="{Binding}" ItemsSource="{Binding Persons}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBox Text="{Binding Name}" />
<ComboBox Name="cb" ItemsSource="{Binding ElementName=lb, Path=DataContext.Types}" SelectedValue="{Binding TheType, Mode=TwoWay, Converter={StaticResource EnumToStringConverter}}" />
<DockPanel Visibility="{Binding ElementName=cb, Path=SelectedValue, Converter={StaticResource NoneToCollapsedConverter}}">
<Label Content="Sentence: " DockPanel.Dock="Left"/>
<TextBlock Text="{Binding TheType, Converter={StaticResource EnumToStringConverter}}" DockPanel.Dock="Right" VerticalAlignment="Center" />
</DockPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Note that you need three converters. One sets the Visibility of The Sentence part to Collapsed, it the type is None:
public class NoneToCollapsedConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value.ToString() == "None")
return Visibility.Collapsed;
return Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
The two others are self descriptive:
public class EnumToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value.ToString();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return Enum.Parse(typeof(MyTypes), value.ToString());
}
}
and
public class EnumToSentenceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
switch ((MyTypes)value)
{
case MyTypes.IsA:
break;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Hope it helps.
I feel like this should be easy, but I am stumped.
I am trying to bind a ComboBox.IsEnabled property to something like "Is an item currently selected on the DataGrid" property using MVVM. This way if no item is selected on the DataGrid, the ComboBox will be disabled.
Is there a DataGrid property that registers True/False when an item is selected, or do I need to do something with the SelectedItems.Count property?
I am trying to do this with as little code as possible before I write a converter or custom property.
//xaml
<DataGrid SelectedItem="{Binding SelectedModelItem}"/>
<ComboBox IsEnabled={Binding IsItemSelected } />
//VM (You will need to implement INotifyPropertyChanged in your ViewModel)
public bool IsItemSelected { get {return null != SelectedModelItem; }
public YourModelType SelectedModelItem{
get{
return selectedModelItem;
}
set{
selectedModelItem = value;
OnPropertyChanged();
}
}
I believe there is no inbuilt property which will say there is one item selected in DataGrid. Instead you can Bind a property to SelectedItem of your DataGrid and Check for SelectedItem is null.
for Example:
<DataGrid
ItemsSource="{Binding ListOfitems}"
SelectedItem="{Binding CurrentItem, Mode=TwoWay}"/>
Then your VM
private object _CurrentItem;
public object CurrentItem
{
get
{
return _CurrentItem;
}
set
{
_CurrentDocument = value;
NotifyPropertyChanged();
//Make your logic for your combobox binding.
}
}
I ended up using a converter to solve the above question. Thank you everybody for your suggestions. I just wanted to make sure I wasn't missing a property, before I implemented this.
XAML
<Control.Resources>
<local:ItemToBoolConverter x:Key="ItemToBoolConverter"/>
</Control.Resources>
<ComboBox IsEnabled="{Binding ElementName=dataGrid, Path=SelectedItems.Count, Converter={StaticResource ItemToBoolConverter}}">
Code Behind
public class ItemToBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
try
{
int? itemCount = value as int?;
if (itemCount < 1)
{
return false;
}
else
{
return true;
}
}
catch { return DependencyProperty.UnsetValue; }
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
view
<Window x:Class="..."
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cvt="clr-namespace:TestTelerikColumnFooter"
Width="300" Height="300"
>
<Window.Resources>
<cvt:SelectionConverter x:Key="SelectionConverter" />
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ComboBox Grid.Row="0" ItemsSource="{Binding Coll1}" IsEnabled="{Binding SelectedPerson, Converter={StaticResource SelectionConverter}}" DisplayMemberPath="FirstName" Margin="6"/>
<DataGrid Grid.Row="1" IsReadOnly="True" ItemsSource="{Binding Coll2}" SelectedItem="{Binding SelectedPerson}" Margin="6"/>
</Grid>
MainViewmodel:
public class MainViewModel : INotifyPropertyChanged
{
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public float Val { get; set; }
}
private object _selectedPerson;
public object SelectedPerson
{
get { return _selectedPerson; }
set
{
_selectedPerson = value;
OnPropertyChanged("SelectedPerson");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
private bool _isItemSelected;
public bool IsItemSelected
{
get { return _isItemSelected; }
set
{
if (value == _isItemSelected)
return;
_isItemSelected = value;
OnPropertyChanged("IsItemSelected");
}
}
private ObservableCollection<Person> _coll1;
public ObservableCollection<Person> Coll1
{
get
{
return _coll1 ?? (_coll1 = new ObservableCollection<Person>());
}
}
private ObservableCollection<Person> _coll2;
public ObservableCollection<Person> Coll2
{
get
{
return _coll2 ?? (_coll2 = new ObservableCollection<Person>());
}
}
public MainViewModel()
{
Coll1.Add(
new Person
{
FirstName = "TOUMI",
LastName = "Redhouane",
Val = 12.2f
});
Coll1.Add(
new Person
{
FirstName = "CHERCHALI",
LastName = "Karim",
Val = 15.3f
});
Coll2.Add(
new Person
{
FirstName = "TOUMI",
LastName = "Djamel",
Val = 12.2f
});
Coll2.Add(
new Person
{
FirstName = "CHERCHALI",
LastName = "Redha",
Val = 12.2f
});
}
}
MainWindow:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
}
converter :
public class SelectionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value != null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I have a ListView with an ComboBox in its ItemTemplate. The ComboBox is also bound to the same list as of the ListView with a Converter. The ComboBox is populated properly but the SelectedItem doesn't show up.
I have tried overriding the Equals method of the underlying object too.
XAML:
<ListView x:Name="FactorsListView"
ItemsSource="{Binding FactorList}" SelectedItem="{Binding SelectedFactor, Mode=OneWayToSource}"
ScrollViewer.CanContentScroll="False">
<ListView.ItemTemplate>
<DataTemplate>
<Grid d:DesignWidth="461.333" d:DesignHeight="368.96">
<StackPanel>
<Grid Height="30.96" Visibility="{Binding IsChecked, ElementName=Monetary, Converter={StaticResource BoolToVis}}">
<Label Content="Related Quantitative Factor:" HorizontalAlignment="Left" Margin="10,0,0,0" VerticalAlignment="Top"/>
<ComboBox Margin="171.707,4,10,0" VerticalAlignment="Top" Width="Auto" ItemsSource="{Binding DataContext.FactorList, ElementName=UcGrid, Converter={StaticResource QtyFacConverter}}" SelectedItem="{Binding RelatedQuantityFactor}" DisplayMemberPath="Name"/>
</Grid>
</StackPanel>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
<ListView>
Converter:
public class FactorConverters : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
ObservableCollection<Factor> givenList = value as ObservableCollection<Factor>;
ObservableCollection<Factor> rList = new ObservableCollection<Factor>();
if (givenList != null)
{
foreach(Factor factor in givenList)
{
if (!factor.IsMonetary)
{
rList.Add(factor);
}
}
}
return rList;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
}
Factor Class:
public class Factor : ModelBase
{
private Factor _RelatedQuantityFactor;
public Factor RelatedQuantityFactor
{
get
{
return _RelatedQuantityFactor;
}
set
{
if (_RelatedQuantityFactor != value)
{
_RelatedQuantityFactor = value;
RaisePropertyChanged("RelatedQuantityFactor");
}
}
}
public override bool Equals(object obj)
{
if (obj == null || !(obj is Factor))
{
return false;
}
else
{
bool res = ((Factor)obj).ID == this.ID;
return res;
}
}
}
FactorsViewModel class:
public class FactorsViewModel : ViewModelBase
{
private ObservableCollection<Factor> _FactorList;
private RevenueItem _SelectedRevenueItem;
private ObservableCollection<Factor> _UniversalFactors;
private Factor _SelectedFactor;
private ObservableCollection<Factor> _QuantitativeFactorHelperList;
public ObservableCollection<Factor> FactorList
{
get
{
return _FactorList;
}
set
{
if (_FactorList != value)
{
_FactorList = value;
AttachFactorListner(value);
}
}
}
private void AttachFactorListner(ObservableCollection<Factor> value)
{
foreach (Factor factor in value)
{
factor.PropertyChanged += factor_PropertyChanged;
}
RaisePropertyChanged("FactorList");
}
void factor_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsMonetary")
{
RaisePropertyChanged("FactorList");
}
}
public RevenueItem SelectedRevenueItem
{
get
{
return _SelectedRevenueItem;
}
set
{
if (_SelectedRevenueItem != value)
{
_SelectedRevenueItem = value;
RaisePropertyChanged("SelectedRevenueItem");
}
}
}
public ObservableCollection<Factor> UniversalFactors
{
get
{
return _UniversalFactors;
}
set
{
if (_UniversalFactors != value)
{
_UniversalFactors = value;
RaisePropertyChanged("UniversalFactors");
}
}
}
public Factor SelectedFactor
{
get
{
return _SelectedFactor;
}
set
{
if (_SelectedFactor != value)
{
_SelectedFactor = value;
RaisePropertyChanged("SelectedFactor");
}
}
}
public ObservableCollection<Factor> QuantitativeFactorHelperList
{
get
{
return _QuantitativeFactorHelperList;
}
set
{
if (_QuantitativeFactorHelperList != value)
{
_QuantitativeFactorHelperList = value;
RaisePropertyChanged("QuantitativeFactorHelperList");
}
}
}
public FactorsViewModel(RevenueItem revenueItem)
{
_SelectedRevenueItem = revenueItem;
_FactorList = revenueItem.Factors;
AttachFactorListner(_FactorList);
}
}
The View and Viewmodels: PostImg Link
You should bind SelectedItem to Some Property in the ViewModel
As I saw in your code you bound it to the Property RelatedQuantityFactor from your model Factor not from ViewModel
Let's see MVVM pattern.
ViewModel should inherit from InotifyPropertyChanged so that it can update View.
Model is Object which include Property and Behavior,but it doesn't communicate with View
So,if you wang to update your UI ,you should use Data Binding.
I think Factor is ViewModel.
public class Factor: ModelBase
{
private ItemViewModel _selectedFactor;
Public ItemViewModel SelectedFactor
{
get
{
return _selectedFactor;
}
set
{
_selectedFactor = value;
RaisePropertyChanged("RelatedQuantityFactor");
}
}
}
you also should change the Binding Mode.
SelectedItem="{Binding SelectedFactor, Mode=OneWay}"
Finally, don't forget to assign DataContext
FactorsListView.DataContext = new Factor();
Just some question:
Do the viewmodel contain any variable name "RelatedQuantityFactor"?
Try to post your code behind which include d initialization of data context and viewmodel if you still face any problem .
Update :
You can try to put breakpoint into your setter and getter of RelatedQuantityFactor to investigate whether it update your relatedquantityfactor as you expected
Update 2:
See this Difference between SelectedItem, SelectedValue and SelectedValuePath.
Hope its helps :)