Special item to represent null values for ComboBox - c#

I'm implementing a user control in WPF using MVVM pattern. I want the control to contain an ItemsControl, specially a ComboBox, that contains a list of People. I want the first menu item to be labelled 'No Person' and bind to null on the data source, while the remaining items are names of people that bind to Person objects.
The code for Person and the view model is as follows:
namespace NoValueItem
{
public class Person : IEquatable<Person>
{
public int Id { get; set; }
public string Name { get; set; }
public static bool Equals(Person a, Person b)
{
if (a == null)
return b == null;
return a.Equals(b);
}
public bool Equals(Person other)
{
if (ReferenceEquals(this, other))
return true;
if (ReferenceEquals(null, other))
return false;
return this.Name.Equals(other.Name);
}
public override bool Equals(object obj)
{
return Equals(obj as Person);
}
public override int GetHashCode()
{
return Name.GetHashCode();
}
}
public class ViewModel : INotifyPropertyChanged
{
public ObservableCollection<Person> ListOfPersons { get; } =
new ObservableCollection<Person> {
null,
new Person() { Id = 1, Name = "Alice" },
new Person() { Id = 2, Name = "Bob" },
new Person() { Id = 3, Name = "Charlie" }
};
private Person _SelectedPerson;
public Person SelectedPerson
{
get
{
return _SelectedPerson;
}
set
{
if (!Person.Equals(value, _SelectedPerson)) {
_SelectedPerson = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class NullSubstituteConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value ?? parameter;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (parameter == null)
return value;
return parameter.Equals(value) ? null : value;
}
}
}
And the view:
<Window x:Class="NoValueItem.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:NoValueItem"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid d:DataContext="{d:DesignInstance local:ViewModel}" VerticalAlignment="Center">
<Grid.DataContext>
<local:ViewModel/>
</Grid.DataContext>
<ComboBox ItemsSource="{Binding ListOfPersons}"
SelectedItem="{Binding SelectedPerson}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</Window>
At run-time, I get 4 items as I would expect: 1 blank menu item followed by 3 non-blank menu items, one for each Person in the ViewModel.ListOfPersons collection, with the item text bound to the Name property of the Person.
I would like the first blank item to instead show the text 'No Person'. How can I do this?
One thing I've tried is using the following data converter, that converts a null reference to the object specified in the converter parameter:
namespace NoValueItem
{
public class NullSubstituteConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value ?? parameter;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (parameter == null)
return value;
return parameter.Equals(value) ? null : value;
}
}
}
I then made the following changes to the view:
Added the NullSubstituteConverter from above as a static resource.
Added Person object as a static resources to represent the 'No Person' item and gave it the key 'NullPerson'.
Set the NullSubstituteConverter resource as the Converter for the binding for the SelectedItem property of the ComboBox.
Set the NullSubstituteConverter resource as the Converter for items in the data template for the ComboBox, so that the null item in the items source is converted to an the NullPerson object.
Here's the updated view:
<Window x:Class="NoValueItem.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:NoValueItem"
mc:Ignorable="d"
Title="MainWindow" Height="300" Width="300">
<Grid d:DataContext="{d:DesignInstance local:ViewModel}" VerticalAlignment="Center">
<Grid.Resources>
<local:Person x:Key="NullPerson">
<local:Person.Id>0</local:Person.Id>
<local:Person.Name>No Person</local:Person.Name>
</local:Person>
<local:NullSubstituteConverter x:Key="NullSubstituteConverter"/>
</Grid.Resources>
<Grid.DataContext>
<local:ViewModel/>
</Grid.DataContext>
<ComboBox ItemsSource="{Binding ListOfPersons}"
SelectedItem="{Binding SelectedPerson,
Converter={StaticResource NullSubstituteConverter},
ConverterParameter={StaticResource NullPerson}}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock DataContext="{Binding Converter={StaticResource NullSubstituteConverter},
ConverterParameter={StaticResource NullPerson}}"
Text="{Binding Name}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</Window>
This is closer to what I want. The blank menu item is now showing 'No Person', but there are still 2 problems:
When the view is first loaded, the 'No Person' item isn't automatically selected by default.
It's not possible to select the 'No Person' item.
I welcome any suggestions on how I can get the 'No Person' menu item working. It can be based on my approach above, or completely different approach as long as it works!

Related

How to bind an ObservableCollection to a TreeView in a hierarchical way?

I know this may be a duplicate but no solution works for me. So I have a class Technik which has these properties:
public class Technik
{
public bool checkedTe { get; set; }
public int TechnikID { get; set; }
public string anlagengruppe { get; set; }
public string techniktyp { get; set; }
public string anlage { get; set; }
public string bemerkung { get; set; }
}
Now I have a DataTable with 216 rows and each row is getting into a Technik object that is added into my ObservableCollection<Technik> like:
foreach (DataRow dr in dtTechnik.Rows)
{
Technik technik = new Technik();
technik.checkedTe = (bool)dr.ItemArray[0];
technik.TechnikID = (int)dr.ItemArray[1];
technik.anlagengruppe = (string)dr.ItemArray[2];
technik.techniktyp = (string)dr.ItemArray[3];
technik.anlage = (string)dr.ItemArray[4];
technik.bemerkung = (string)dr.ItemArray[5];
TechnikCollection.Add(technik);
}
I want to bind my ObservableCollection like:
* anlagengruppe
* techniktyp
*anlage
* TechnikID
Right now I'm getting nowhere, so maybe you guys out there can help me.
Actual my tree view looks like this:
<TreeView x:Name="treeView" HorizontalAlignment="Left" Height="850" Margin="10,0,0,0" VerticalAlignment="Top" Width="464"
ScrollViewer.CanContentScroll="True" ScrollViewer.VerticalScrollBarVisibility="Visible" ItemsSource="{Binding TechnicTable}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding TechnicTable}">
<TextBlock Text="{Binding Path=anlagengruppe}" />
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding techniktyp}" />
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Edit:
Maybe some of you think that my tree view ItemsSource is not the correct collection, this is the right one, there is some more code where I change collections.
I am skeptical of this design. Why do you feel it's useful and helpful to the user to present a single object and its property values as if that object had some hierarchical structure to it?
If all you're trying to do is impose some visual structure on the user interface, that's easily done without using TreeView. For example:
class TableItem
{
public string Property1 { get; set; }
public string Property2 { get; set; }
public string Property3 { get; set; }
public TableItem() { }
public TableItem(string property1, string property2, string property3)
{
Property1 = property1;
Property2 = property2;
Property3 = property3;
}
}
class ViewModel
{
public ObservableCollection<TableItem> TableItems { get; } = new ObservableCollection<TableItem>();
}
<Window x:Class="TestSO46300831HiearchicalObservable.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:l="clr-namespace:TestSO46300831HiearchicalObservable"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<l:ViewModel>
<l:ViewModel.TableItems>
<l:TableItem Property1="Item #1, property #1"
Property2="Item #1, property #2"
Property3="Item #1, property #3"/>
<l:TableItem Property1="Item #2, property #1"
Property2="Item #2, property #2"
Property3="Item #2, property #3"/>
<l:TableItem Property1="Item #3, property #1"
Property2="Item #3, property #2"
Property3="Item #3, property #3"/>
</l:ViewModel.TableItems>
</l:ViewModel>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type l:TableItem}">
<StackPanel>
<TextBlock Text="{Binding Property1}"/>
<TextBlock Text="{Binding Property1}" Margin="10,0,0,0"/>
<TextBlock Text="{Binding Property1}" Margin="20,0,0,0"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ListBox ItemsSource="{Binding TableItems}"/>
</StackPanel>
</Window>
That said, if you must use TreeView and you want for the view to update as the collection is modified, it seems to me you can accomplish that by using an intermediate collection that implements INotifyCollectionChanged (easily done simply by inheriting ObservableCollection<T> and tracking the original collection. The intermediate collection is needed, so that items can be converted from the original single-object item to a hierarchical item type that can be used with the TreeView class. For example:
class HierarchicalTableItem
{
public string Text { get; }
public IReadOnlyList<HierarchicalTableItem> Items { get; }
public HierarchicalTableItem(string text, HierarchicalTableItem child = null)
{
Text = text;
Items = child != null ? new[] { child } : null;
}
}
class ViewModel
{
public ICommand AddCommand { get; }
public ICommand InsertCommand { get; }
public ICommand RemoveCommand { get; }
public int Index { get; set; }
public ObservableCollection<TableItem> TableItems { get; } = new ObservableCollection<TableItem>();
public ViewModel()
{
AddCommand = new DelegateCommand(() => TableItems.Add(_CreateTableItem()));
InsertCommand = new DelegateCommand(() => TableItems.Insert(Index, _CreateTableItem()));
RemoveCommand = new DelegateCommand(() => TableItems.RemoveAt(Index));
}
private int _itemNumber;
private TableItem _CreateTableItem()
{
_itemNumber = (_itemNumber < TableItems.Count ? TableItems.Count : _itemNumber) + 1;
return new TableItem(
$"Item #{_itemNumber}, property #1",
$"Item #{_itemNumber}, property #2",
$"Item #{_itemNumber}, property #3");
}
}
class ConvertingObservableCollection<T> : ObservableCollection<object>
{
private readonly IValueConverter _converter;
private readonly ObservableCollection<T> _collection;
public ConvertingObservableCollection(IValueConverter converter, ObservableCollection<T> collection)
{
_converter = converter;
_collection = collection;
_ResetItems();
_collection.CollectionChanged += _OnCollectionChanged;
}
private void _OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
_AddItems(e);
break;
case NotifyCollectionChangedAction.Move:
_RemoveItems(e);
_AddItems(e);
break;
case NotifyCollectionChangedAction.Remove:
_RemoveItems(e);
break;
case NotifyCollectionChangedAction.Replace:
_ReplaceItems(e);
break;
case NotifyCollectionChangedAction.Reset:
_ResetItems();
break;
}
}
private void _ReplaceItems(NotifyCollectionChangedEventArgs e)
{
for (int i = 0; i < e.NewItems.Count; i++)
{
this[i] = _Convert(e.NewItems[i]);
}
}
private void _AddItems(NotifyCollectionChangedEventArgs e)
{
for (int i = 0; i < e.NewItems.Count; i++)
{
Insert(i + e.NewStartingIndex, _Convert(e.NewItems[i]));
}
}
private void _RemoveItems(NotifyCollectionChangedEventArgs e)
{
for (int i = e.OldItems.Count - 1; i >= 0; i--)
{
RemoveAt(i + e.OldStartingIndex);
}
}
private void _ResetItems()
{
Clear();
foreach (T t in _collection)
{
Add(_Convert(t));
}
}
private object _Convert(object value)
{
return _converter.Convert(value, typeof(T), null, null);
}
}
class TableItemHierarchicalConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
TableItem tableItem = value as TableItem;
if (tableItem == null)
{
return Binding.DoNothing;
}
return new HierarchicalTableItem(tableItem.Property1,
new HierarchicalTableItem(tableItem.Property2,
new HierarchicalTableItem(tableItem.Property3)));
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
class ConvertingCollectionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
IValueConverter converter = parameter as IValueConverter;
if (converter == null || value == null ||
value.GetType().GetGenericTypeDefinition() != typeof(ObservableCollection<>))
{
return Binding.DoNothing;
}
Type resultType = typeof(ConvertingObservableCollection<>).MakeGenericType(value.GetType().GenericTypeArguments);
return Activator.CreateInstance(resultType, converter, value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
<Window x:Class="TestSO46300831HiearchicalObservable.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:l="clr-namespace:TestSO46300831HiearchicalObservable"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<l:ViewModel>
<l:ViewModel.TableItems>
<l:TableItem Property1="Item #1, property #1"
Property2="Item #1, property #2"
Property3="Item #1, property #3"/>
<l:TableItem Property1="Item #2, property #1"
Property2="Item #2, property #2"
Property3="Item #2, property #3"/>
<l:TableItem Property1="Item #3, property #1"
Property2="Item #3, property #2"
Property3="Item #3, property #3"/>
</l:ViewModel.TableItems>
</l:ViewModel>
</Window.DataContext>
<Window.Resources>
<l:ConvertingCollectionConverter x:Key="convertingCollectionConverter1"/>
<l:TableItemHierarchicalConverter x:Key="tableItemConverter1"/>
</Window.Resources>
<ScrollViewer>
<StackPanel>
<UniformGrid Columns="4">
<Button Content="Add" Command="{Binding AddCommand}"/>
<Button Content="Insert" Command="{Binding InsertCommand}"/>
<Button Content="Remove" Command="{Binding RemoveCommand}"/>
<TextBox Text="{Binding Index}"/>
</UniformGrid>
<TreeView ItemsSource="{Binding TableItems,
Converter={StaticResource convertingCollectionConverter1},
ConverterParameter={StaticResource tableItemConverter1}}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Items}">
<TextBlock Text="{Binding Text}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</StackPanel>
</ScrollViewer>
</Window>
This alternative relies on three key classes:
ConvertingObservableCollection<T> — This does the work of watching the original collection and presenting converted items according to the current state of that original collection.
ConvertingCollectionConverter — This converts the original collection to the ConvertingObservableCollection<T> object for the purpose of binding to the TreeView.
TableItemHierarchicalConverter — This converts the individual original item objects into a hierarchy of objects suitable for display in the TreeView.
Of course, there is also the simple container class HierarchicalTableItem which is used to represent the hierarchy for each table item.
Ultimately, the key to remember is that for a TreeView, you must be presenting items that have a recursive nature, such that a single HierarchicalDataTemplate element can be used to define how to present each level of the tree. This means that a single data item must have some property that can be used for the ItemsSource of the template, which is itself some type of collection of the same type of data item.

Odd behavior when trying to change a bound RadioButton in WPF

I've bound two radio buttons in my Child window to an Enum in my ViewModel which is constructed in the Main window. The binding works as expected but I have noticed a very odd behavior which I can't solve. I have provided all the code here so you can reconstruct the problem easily for yourself.
Here are the steps to see this odd behavior:
Click on the button in the MainWindow
The ChildWindow opens and the RadioButton is set to User
Choose Automatic and then Choose User again
Close the ChildWindow and reopen it again! Try to change the RadioButton to Automatic. It won't change!
<Window x:Class="RadioButtonBinding.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">
<Button Content="Display Child Window" Click="DisplayChildWindow"/>
</Window>
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
var viewModel = new ViewModel();
DataContext = viewModel;
}
private void DisplayChildWindow(object sender, RoutedEventArgs e)
{
var win = new ChildWindow {DataContext = (ViewModel) DataContext};
win.ShowDialog();
}
}
<Window x:Class="RadioButtonBinding.ChildWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:radioButtonBinding="clr-namespace:RadioButtonBinding"
Title="ChildWindow" Height="300" Width="300">
<Window.Resources>
<radioButtonBinding:EnumBooleanConverter x:Key="EnumBooleanConverter"/>
</Window.Resources>
<StackPanel>
<RadioButton Content="Automatic"
GroupName="CalcMode"
IsChecked="{Binding Path=CalcMode,
Converter={StaticResource EnumBooleanConverter},
ConverterParameter={x:Static radioButtonBinding:CalcMode.Automatic}}"/>
<RadioButton Content="Custom"
GroupName="CalcMode"
IsChecked="{Binding Path=CalcMode,
Converter={StaticResource EnumBooleanConverter},
ConverterParameter={x:Static radioButtonBinding:CalcMode.User}}"/>
</StackPanel>
</Window>
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private CalcMode calcMode = CalcMode.User;
public CalcMode CalcMode
{
get { return calcMode; }
set
{
calcMode = value;
RaisePropertyChanged("CalcMode");
}
}
private void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler == null) return;
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class EnumBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var paramEnum = parameter as Enum;
var valueEnum = value as Enum;
return Equals(paramEnum, valueEnum);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
var parameterEnum = parameter as Enum;
if (parameterEnum == null)
return DependencyProperty.UnsetValue;
return parameterEnum;
}
}
public enum CalcMode : byte
{
Automatic,
User,
}
UPDATE:
I suspect it must be the Converter but I don't know why? It just falls into a loop.
EDIT
What about converting the enum to bool as follows?
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (parameter == null || !(bool)value)
return DependencyProperty.UnsetValue;
var parameterEnum = parameter as Enum;
return parameterEnum;
}

Flexible way to show a lot of columns in datagrid

I have a lot of columns in WPF DataGrid containg orders, over 80. They can be visible or hidden depending on view options menu. For now I am doing options menu separately, orders view model separately, columns visibility and headers processing in OnAutoGeneratingColumn event. So I have 3 different classes (ViewOptions, OrdersViewModel, ViewOptionsViewModel) and a lot of logic in event handler. Also It will be necessary to modify code in 4 places on adding/removing columns.
Is there a better way to bind menu headers to columns headers, as well as binding columns visibility (DataGrid) to checkboxes in menu (ViewOptionsViewModel)?
use binding ,When I checked the "Display Property 1 ,the Column visible
Xaml:
<Window x:Class="WpfApplication6.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:test="clr-namespace:WpfApplication6"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<test:Bool2Visibility x:Key="bool2Visibility"/>
<test:BindingProxy x:Key="bpProperty1"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<CheckBox Content="Display Property1" IsChecked="{Binding Source={StaticResource bpProperty1},Path=Data,Mode=OneWayToSource}"/>
</StackPanel>
<DataGrid Grid.Row="1" ItemsSource="{Binding}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="property1" Binding="{Binding Property1}" Visibility="{Binding Source={StaticResource bpProperty1},Path=Data,Converter={StaticResource bool2Visibility}}"/>
<DataGridTextColumn Header="property2" Binding="{Binding Property2}"/>
<DataGridTextColumn Header="property3" Binding="{Binding Property3}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
c# code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<TestData> list = new List<TestData>();
for (int i = 0; i < 10; i++)
{
TestData item = new TestData();
item.Property1 = "property1" + i.ToString();
item.Property2 = "property2" + i.ToString();
item.Property3 = "property3" + i.ToString();
list.Add(item);
}
this.DataContext = list;
}
}
public class TestData
{
public string Property1 { get; set; }
public string Property2 { get; set; }
public string Property3 { get; set; }
}
public class Bool2Visibility : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool flag = false;
if (value != null)
{
flag = System.Convert.ToBoolean(value);
}
return flag ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
public class BindingProxy : Freezable
{
#region Overrides of Freezable
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
#endregion
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
// Using a DependencyProperty as the backing store for Data. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}

How to determine if an WPF Datagrid item is selected, and bind to the true/false result

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();
}
}

WPF radio button binding - getting an error in this code here?

Trying to get radiobuttons binding working but getting a run time error with the code below. Want the radio buttons to act such that only one can be selected at a time, and that they bind correctly in a 2 way fashion. Error text is.
"The invocation of the constructor on
type 'testapp1.MainWindow' that
matches the specified binding
constraints threw an exception"
<Window x:Class="testapp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:testapp1" Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.Resources>
<l:EnumBooleanConverter x:Key="enumBooleanConverter" />
</Grid.Resources>
<StackPanel >
<RadioButton IsChecked="{Binding Path=VeryLovelyEnum, Converter={StaticResource enumBooleanConverter}, ConverterParameter=FirstSelection}">first selection</RadioButton>
<RadioButton IsChecked="{Binding Path=VeryLovelyEnum, Converter={StaticResource enumBooleanConverter}, ConverterParameter=TheOtherSelection}">the other selection</RadioButton>
<RadioButton IsChecked="{Binding Path=VeryLovelyEnum, Converter={StaticResource enumBooleanConverter}, ConverterParameter=YetAnotherOne}">yet another one</RadioButton>
<Label Content="{Binding Path=VeryLovelyEnum}" Height="28" Name="label1" />
</StackPanel>
</Grid>
</Window>
And code:
namespace testapp1
{
public partial class MainWindow : Window
{
public TestModel _model;
public MainWindow()
{
InitializeComponent();
InitializeComponent();
_model = new TestModel();
this.DataContext = _model;
}
}
public enum MyLovelyEnum
{
FirstSelection,
TheOtherSelection,
YetAnotherOne
};
public class TestModel : DependencyObject
{
public MyLovelyEnum VeryLovelyEnum
{
get { return (MyLovelyEnum)GetValue(VeryLovelyEnumProperty); }
set { SetValue(VeryLovelyEnumProperty, value); }
}
public static readonly DependencyProperty VeryLovelyEnumProperty =
DependencyProperty.Register("VeryLovelyEnum", typeof(MyLovelyEnum), typeof(TestModel), new UIPropertyMetadata(0));
// from http://stackoverflow.com/questions/397556/wpf-how-to-bind-radiobuttons-to-an-enum
public class EnumBooleanConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string parameterString = parameter as string;
if (parameterString == null)
return DependencyProperty.UnsetValue;
if (Enum.IsDefined(value.GetType(), value) == false)
return DependencyProperty.UnsetValue;
object parameterValue = Enum.Parse(value.GetType(), parameterString);
return parameterValue.Equals(value);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string parameterString = parameter as string;
if (parameterString == null)
return DependencyProperty.UnsetValue;
return Enum.Parse(targetType, parameterString);
}
#endregion
}
}
The button & label is there from a previous test I did - I just left in...
The following declaration uses an integer instead of a default enum-value. Could be your instantiation problem...
public static readonly DependencyProperty VeryLovelyEnumProperty =
DependencyProperty.Register("VeryLovelyEnum", typeof(MyLovelyEnum), typeof(TestModel), new UIPropertyMetadata(0));
Try something like...
public static readonly DependencyProperty VeryLovelyEnumProperty =
DependencyProperty.Register("VeryLovelyEnum", typeof(MyLovelyEnum), typeof(TestModel), new UIPropertyMetadata(MyLovelyEnum.FirstSelection));

Categories

Resources