WPF ComboBox SelectedItem when bound to an enum - wpf

I have a WPF ComboBox bound to a list of a class which contains an enum.
This all works fine, my question is at the end of this post, first the code:
Here is the class:
public class FILTER_TEST
{
public FilterType Filter { get; private set; }
public string Description { get; private set; }
public static List<FILTER_TEST> CreateFilters()
{
var list = new List<FILTER_TEST>();
list.Add(new FILTER_TEST() { Filter = FilterType.CheckNone, Description = "Uncheck all" });
list.Add(new FILTER_TEST() { Filter = FilterType.CheckAll, Description = "Check all" });
list.Add(new FILTER_TEST() { Filter = FilterType.CheckCustom, Description = "Custom check" });
return list;
}
}
Here is the enum FilterType:
public enum FilterType
{
CheckNone,
CheckAll,
CheckCustom
}
In my view model I have the following:
public List<FILTER_TEST> FilterNames { get { return FILTER_TEST.CreateFilters(); } }
public FILTER_TEST SelectedFilter
{
get { return selectedFilter; }
set
{
if (value != selectedFilter)
{
selectedFilter = value;
OnPropertyChanged("SelectedFilter");
}
}
}
Also in the view model, I set the SelectedItem of the ComboBox as follows:
SelectedFilter = FilterNames.Where(x => x.Filter == FilterType.CheckNone).FirstOrDefault();
Here is the xaml putting it all together:
<ComboBox DisplayMemberPath="Description" ItemsSource="{Binding FilterNames}"
SelectedItem="{Binding SelectedFilter, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" IsSynchronizedWithCurrentItem="True"/>
My problem is that although the changing of the SelectionItem works, the actual value displayed in the ComboBox doesn’t change.
The initial SelectedItem is “Uncheck all” as, when the window has been loaded, none of the corresponding CheckBox controls (bound to another class which contains a Boolean property) have been checked. What I would like is that when a CheckBox has been checked, then the SelectedItem changes to “Custom check”.
This does indeed change the value of the SelectedItem:
SelectedFilter = FilterNames.Where(x => x.Filter == FilterType.CheckCustom).FirstOrDefault();
But the text shown in the ComboBox is still “Uncheck all”.
Does anyone have an idea as to what I am missing? I am forced to use the 4.0 framework, I don’t know if this is relevant.

I've seen the hint to overwrite Equals() of the type in use as this:
public override bool Equals(object o)
{
if (o is FILTER_TEST)
{
var other = o as FILTER_TEST;
return this.Description == other.Description && this.Filter == other.Filter;
}
else
return false;
}
Now that makes your sample work. Let me come back for a reference on the why.

Related

How to use ReactiveList so UI is updated when items are added/removed/modified

I'm creating a WinForms application with a DataGridView. The DataSource is a ReactiveList. Adding new items to the list however does not update the UI.
ViewModel
public class HomeViewModel: ReactiveObject
{
public ReactiveCommand<object> AddCmd { get; private set; }
ReactiveList<Model> _models;
public ReactiveList<Model> Models
{
get { return _models; }
set { this.RaiseAndSetIfChanged(ref _models, value); }
}
public HomeViewModel()
{
Models = new ReactiveList<Model>() { new Model { Name = "John" } };
AddCmd = ReactiveCommand.Create();
AddCmd.ObserveOn(RxApp.MainThreadScheduler);
AddCmd.Subscribe( _ =>
{
Models.Add(new Model { Name = "Martha" });
});
}
}
public class Model
{
public string Name { get; set; }
}
View
public partial class HomeView : Form, IViewFor<HomeViewModel>
{
public HomeView()
{
InitializeComponent();
VM = new HomeViewModel();
this.OneWayBind(VM, x => x.Models, x => x.gvData.DataSource);
this.BindCommand(VM, x => x.AddCmd, x => x.cmdAdd);
}
public HomeViewModel VM { get; set; }
object IViewFor.ViewModel
{
get { return VM; }
set { VM = (HomeViewModel)value; }
}
HomeViewModel IViewFor<HomeViewModel>.ViewModel
{
get { return VM; }
set { VM = value; }
}
}
The view always show "John".
Debugging Subscribe show added items.
Tried it with ObservableCollection same result.How to use ReactiveList so UI is updated when new items are added
Tried it with IReactiveDerivedList same result. Does ReactiveUI RaiseAndSetIfChanged fire for List<T> Add, Delete, Modify?
I think what you want is a ReactiveBindingList rather than a ReactiveList. This is a WinForms specific version of the ReactiveList for binding purposes.
You should use BindingList.
reference :
"If you are bound to a data source that does not implement the IBindingList interface, such as an ArrayList, the bound control's data will not be updated when the data source is updated. For example, if you have a combo box bound to an ArrayList and data is added to the ArrayList, these new items will not appear in the combo box. However, you can force the combo box to be updated by calling the SuspendBinding and ResumeBinding methods on the instance of the BindingContext class to which the control is bound."
https://learn.microsoft.com/en-us/dotnet/desktop/winforms/controls/how-to-bind-a-windows-forms-combobox-or-listbox-control-to-data?view=netframeworkdesktop-4.8
Or
ReactiveBindingList
It work fine for me. !!!

Dynamic Row and Column Creation using WPF and MVVM

Note: I'm using MVVM Light Toolkit and MahApps.Metro.
I've checked the answers but it doesn't seem like any of them relate to my question.
I have a Grid whose columns and header should be dynamically created. The number and value of columns is unknown to view, and the number of rows is unknown to view.
Columns, rows and data in the rows represent a Database Table. All data is present in the ViewModel.
I have an ObservableCollection<ServerRow> ServerRows; in my ViewModel.
Server Row object is a Model that looks like this:
public class ServerRow : ObservableObject
{
private ObservableCollection<ServerColumn> _columns;
public ObservableCollection<ServerColumn> Columns
{
get { return _columns; }
set { Set(() => Columns, ref _columns, value); }
}
}
This is a ServerColumn class :
public class ServerColumn : ObservableObject
{
private string _name;
private string _type;
private string _value;
public string Name
{
get { return _name; }
set { Set(() => Name, ref _name, value); }
}
public string Type
{
get { return _type; }
set { Set(() => Type, ref _type, value); }
}
public string Value
{
get { return _value; }
set { Set(() => Value, ref _value, value); }
}
}
The Idea was to Bind DataGrid to ObservableCollection<ServerRow> ServerRows;, and then generate the Columns depending on the ServerRow object which has ServerColumns which in turn have Name (should be a header of the column), Type as the datatype of column data, and Value as the value which should be represented in every row/column.
My XAML is pretty simple (because it's not complete, and of course- not working)
<DataGrid AutoGenerateColumns="True" ItemsSource="{Binding ServerRows}"/>
How do I write the XAML properly to achieve what I'm trying to do?
This is the result, which makes sense because Grid is trying to show a collection of objects inside a single Column and calling its ToString() method.
I've had this problem before too.
If you look at what is done here:
https://github.com/taori/WMPR/blob/0a81bc6a6a4c6fc36edc4cbc99f0cfa8a2b8871c/src/WMPR/WMPR.Client/ViewModels/Sections/ReportEvaluationViewModel.cs#L503
You provide the iteratable collection as ObservableCollection<object> when the underlying structure is actually of type DynamicGridCell, which uses a DynamicGridCellDescriptor which can be found at
DynamicGridCell:
public class DynamicGridCell : DynamicObject, ICustomTypeDescriptor, IDictionary<string, object>
{
private readonly Dictionary<string, object> _values = new Dictionary<string, object>();
AttributeCollection ICustomTypeDescriptor.GetAttributes()
{
return new AttributeCollection();
}
string ICustomTypeDescriptor.GetClassName()
{
return nameof(DynamicGridCell);
}
string ICustomTypeDescriptor.GetComponentName()
{
return null;
}
TypeConverter ICustomTypeDescriptor.GetConverter()
{
return null;
}
EventDescriptor ICustomTypeDescriptor.GetDefaultEvent()
{
return null;
}
PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty()
{
return null;
}
object ICustomTypeDescriptor.GetEditor(Type editorBaseType)
{
return null;
}
EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
{
return null;
}
EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes)
{
return null;
}
private PropertyDescriptor[] CreatePropertyDescriptors()
{
var result = new List<PropertyDescriptor>();
foreach (var pair in _values)
{
result.Add(new DynamicGridCellDescriptor(pair.Key));
}
return result.ToArray();
}
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
{
var result = new PropertyDescriptorCollection(CreatePropertyDescriptors());
return result;
}
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
{
var result = new PropertyDescriptorCollection(CreatePropertyDescriptors());
return result;
}
object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
public IEnumerator GetEnumerator()
{
return _values.GetEnumerator();
}
IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator()
{
return _values.GetEnumerator();
}
void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item)
{
_values.Add(item.Key, item.Value);
}
void ICollection<KeyValuePair<string, object>>.Clear()
{
_values.Clear();
}
bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item)
{
return _values.Contains(item);
}
void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
{
}
bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item)
{
if (_values.ContainsKey(item.Key))
{
_values.Remove(item.Key);
return true;
}
return false;
}
public int Count => _values.Count;
bool ICollection<KeyValuePair<string, object>>.IsReadOnly => false;
public bool ContainsKey(string key)
{
return _values.ContainsKey(key);
}
public void Add(string key, object value)
{
_values.Add(key, value);
}
bool IDictionary<string, object>.Remove(string key)
{
return _values.Remove(key);
}
public bool TryGetValue(string key, out object value)
{
return _values.TryGetValue(key, out value);
}
public object this[string key]
{
get { return _values[key]; }
set
{
if (_values.ContainsKey(key))
{
_values[key] = value;
}
else
{
_values.Add(key, value);
}
}
}
public ICollection<string> Keys => _values.Keys;
public ICollection<object> Values => _values.Values;
}
DynamicGridCellDescriptor
public class DynamicGridCellDescriptor : PropertyDescriptor
{
public DynamicGridCellDescriptor(string name) : base(name, null)
{
}
public override bool CanResetValue(object component)
{
return true;
}
public override object GetValue(object component)
{
return ((DynamicGridCell) component)[Name];
}
public override void ResetValue(object component)
{
((DynamicGridCell) component)[Name] = null;
}
public override void SetValue(object component, object value)
{
((DynamicGridCell) component)[Name] = value;
}
public override bool ShouldSerializeValue(object component)
{
return false;
}
public override Type ComponentType => typeof(DynamicGridCell);
public override bool IsReadOnly => false;
public override Type PropertyType => typeof(object);
}
Just make sure that the property you bind to is of type ObservableCollection<object> anyways - otherwise for me automatic grid column generation did not work.
You have some logical issues.
When you set the ItemsSource of a DataGrid the bound collection will be used to create rows and if you don't change it the property AutoGenerateColumns is set to true. In this case the DataGrid will generate a column for each property in the bound collection and this is exactly what is happening in your sample.
You bound an instance with a property 'Columns' and get a DataGrid column which is named 'Columns'. And you get as much rows as you have entries in this property displayed as '(Collection)' because ServerColumn inherits from ObservableObject.
You can set AutoGenerateColumns to false and have to create the columns by your own; normally in xaml => hard coded.
If you really want to have dynamically generate the columns you have to write your own logic to create and bind the columns. I've done that once and it's pain in the ass if you want to have it generic.
If you want a DataGrid with dynamic columns where the user can change values it's more tricky then a read only one.
One approach could be having a ObservableCollection<string> for the column names and another one ObservableCollection which stores your ViewModels for each row.
If both rows and columns really need to be dynamic, your best choice is to use two nested ItemControls, the outer one representing rows, the inner one columns:
<ItemsControl ItemsSource="{Binding Rows}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding Columns}" ItemTemplateSelector="{StaticResource ColumnTemplateSelector}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
This allows you to display different type of columns, by defining a template selector that might look somewhat similar to the following:
public class ColumnTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var column = item as ServerColumn;
switch (column.Type)
{
case "Text":
return TextTemplate;
case "Number":
return NumberTemplate;
case "Image":
return ImageTemplate;
}
// Fallback
return TextTemplate;
}
public DataTemplate TextTemplate { get; set; }
public DataTemplate NumberTemplate { get; set; }
public DataTemplate ImageTemplate { get; set; }
}
...based on each column's type, a different template would be referenced (all of these template obviously need to be defined somewhere and referenced as StaticResource. (This even allows easy creation of changeable (not read-only) grids.)
Note that, instead of the outer ItemsControl, you can of course use ListView or any other control that is derived from ItemsControl. Using a ListView might be useful if you need automatic scrolling, for example.

WPF ListView selection upon focus lost

I am currently learning and working on simple WPF (MVVM pattern) application, which allows to select items from one listview (available items) to another (order) and create an order class instance once 'buy' button is being pressed.
My problem is that once I click on first listview - item is selected and I am not able to deselect it once focus is lost.
I have learnt a lot about event and commands in MVVM, but things a really mixed in my head. Could you please guide me to the simple way how it's possible to 'refresh'/deselect all items once focus is lost on listview?
Thank you.
Here's a quick and dirty example using bindlings. You'll probably have to adjust some it for your needs:
The XAML
<Grid>
<ListBox Name="customerList1"
ItemsSource="{Binding List1Customers}"
SelectedItem="{Binding List1SelectedCustomer, Mode=TwoWay}"/>
<ListBox Name="customerList2"
ItemsSource="{Binding List2Customers}"
SelectedItem="{Binding List2SelectedCustomer, Mode=TwoWay}"/>
</Grid>
The ViewModel
public class Customer
{
public int id { get; set; }
}
public class MyViewModel
{
// This is the collection the first list gets its data from
private ObservableCollection<Customer> list1Customers;
public ObservableCollection<Customer> List1Customers
{
get { return list1Customers; }
set
{
if (list1Customers != value)
{
list1Customers = value;
}
}
}
// This is the customer selected in the first list
private Customer list1SelectedCustomer;
public Customer List1SelectedCustomer
{
get { return list1SelectedCustomer; }
set
{
if (list1SelectedCustomer != value)
{
list1SelectedCustomer = value;
}
}
}
// This is the collection the second list gets its data from
private ObservableCollection<Customer> list2Customers;
public ObservableCollection<Customer> List2Customers
{
get { return list2Customers; }
set
{
if (list2Customers != value)
{
list2Customers = value;
}
}
}
// This is the customer selected in the second list
private Customer list2SelectedCustomer;
public Customer List2SelectedCustomer
{
get { return list2SelectedCustomer; }
set
{
if (list2SelectedCustomer != value)
{
list2SelectedCustomer = value;
}
}
}
public void MoveCustomer(int id)
{
// Find the customer from list 1
var customerToMove = List1Customers.Where(x => x.id == id).FirstOrDefault();
// If it was found...
if (customerToMove != null)
{
// Make the first customer null, which un selects it in the list
List1SelectedCustomer = null;
// Remove the customer from list 1, or comment out if you dont want it removed
List1Customers.Remove(customerToMove);
// Add the customer to list 2
List2Customers.Add(customerToMove);
// Make the newly addec customer in list 2 selected
List2SelectedCustomer = List2Customers.Where(x => x.id == id).FirstOrDefault();
}
}
}

Combobox in settingsflyout does not show selected values when opening it again

This seemed so simple but has turned into a nightmare for me. Everything works great, i can select a value and it's reported back to the view model.
Problem:
User opens the settings flyout and selects a value. User exits the flyout.
User reopens the settings flyout and there is no selected value in the combobox. The value exists in the view model though.
Scenario:
Combobox in a Settingsflyout.
<ComboBox x:Name="defaultComboBox" SelectedItem="{Binding UserSettings.DefaultAccount, Mode=TwoWay}" ItemsSource="{Binding UserAccounts}" DisplayMemberPath="CustomName">
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Loaded">
<core:InvokeCommandAction Command="{Binding UserAccountComboboxLoadedCommand}" CommandParameter="{Binding ElementName=defaultAccountComboBox}"/>
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</ComboBox>
ViewModelCode:
public void Open(object parameter, Action successAction)
{
logger.LogProgress("Opened UserSettingsFlyoutView.");
UserSettings.DefaultAccount = UserAccounts.FirstOrDefault(u => u.AccountID.ToString().Equals(userSettings.DefaultAccountGuid,StringComparison.CurrentCultureIgnoreCase));
}
public CrossThreadObservableCollection<UserAccount> UserAccounts
{
get
{
try
{
return dbContext.RetrieveAllUserAccounts();
}
catch(Exception e)
{
logger.LogError("Error happened when retrieving user-accounts from secure data store Error: " + e.Message, e.ToString());
return new CrossThreadObservableCollection<UserAccount>();
}
}
}
private IProvideUserSetting userSettings;
public IProvideUserSetting UserSettings
{
get { return userSettings; }
set { userSettings = value; OnPropertyChanged("UserSettings"); }
}
UserSettings class:
private string defaultAccountGuid;
[DataMember]
public string DefaultAccountGuid
{
get { return defaultAccountGuid; }
set { defaultAccountGuid = value; OnPropertyChanged("DefaultAccountGuid"); }
}
private UserAccount defaultAccount;
[IgnoreDataMember]
public UserAccount DefaultAccount
{
get { return defaultAccount; }
set {
defaultAccount = value;
if (defaultAccount != null)
DefaultAccountGuid = defaultAccount.AccountID.ToString();
OnPropertyChanged("DefaultAccount"); }
}
I tried a version of the code and could not reproduce the issue. Could you provide more code? Is there something else setting the selected item?
Anyway, the type of item in the ItemsSource is different than the type of item for selected item. I would try changing the selected item binding to the same class in the items source.
For example, instead of the viewmodel property UserSettings, make that object type UserAccount.
Something like
private UserAccount _selectedUserAccount { get; set; }
public UserAccount SelectedUserAccount
{
get { return _selectedUserAccount; }
set
{
if (_selectedUserAccount != value)
{
_selectedUserAccount = value;
OnPropertyChanged("SelectedUserAccount");
}
}
}
Edit:
You can add a loaded event handler to your combobox, then locate the viewmodel from the code behind and set the selected item property.
private void ComboBox_Loaded(object sender, RoutedEventArgs e)
{
ComboBox comboBox = sender as ComboBox;
comboBox.SelectedItem =
_viewModel.UserAccounts.Where(x => x.UserAccountString == _viewModel.SelectedUserAccount.UserAccountString);
}

wpf ObservableCollection fill combobox

How do I fill up my combobox to an item of my ObservableCollectio ?
public ObservableCollection<Contacts> contacts = new ObservableCollection<Contacts>();
Item within Contacts is "Grname". Those items need to be binded to it. Prefer by code, because I want to filter out the duplicates (grouping).
class Contacts
{
public string Contact_id { get; set; }
public string Grname { get; set; }
}
UPDATE:
I found it !
ICollectionView contactsView = CollectionViewSource.GetDefaultView(dataGrid1.ItemsSource);
cmbGroup.ItemsSource = contactsView.Groups;
But how to filter my datagrid with the selected item of combobox ?
I've got:
void Filter(object sender, FilterEventArgs e)
{
if (cmbGroup.ItemsSource == contactsView)
{
e.Accepted = true;
}
else
{
e.Accepted = false;
}
}
And Filter is binded in CollectionViewSource in my XAML
For filtering, grouping, sorting etc. you could use a CollectionViewSource. That means something like
ICollectionView contactsView = CollectionViewSource.GetDefaultView(contacts);
// For filtering:
contactsView.Filter += sender => {
// Filter logic here
return true;
}
Then you bind your combobox against the contactsView.

Resources