WPF: two way data binding is not working - c#

I found some silimar questions, but these are not exactly what i need.
I want to bound stackpanel "IsEnabled" value to bool "!IsIterrupted" value of my Items. Here is my XAML file:
<ListView ItemsSource="{Binding Path=Items}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel IsEnabled="{Binding !IsInterrupted, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
<Button Command="{Binding Path=StopThreadCommand, Source={StaticResource viewModel}}" CommandParameter="{Binding Id}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
This is how items looks like:
public class ThreadDecorator : BaseThread , INotifyPropertyChanged
{
...
public event PropertyChangedEventHandler PropertyChanged;
private bool _is_interrupted;
public bool IsInterrupted
{
get { return _is_interrupted; }
set
{
_is_interrupted = value;
OnPropertyChanged("IsInterrupted");
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
...
}
And my ViewModel:
public class ThreadsViewModel : DependencyObject
{
private ThreadsModel _model;
public ThreadsModel Model
{
get { return _model; }
set
{
_model = value;
}
}
public ICollectionView Items
{
get { return (ICollectionView)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register("Items", typeof(ICollectionView), typeof(ThreadsViewModel), new PropertyMetadata(null));
public StopThreadCommand StopThreadCommand { get; set; }
public ThreadsViewModel()
{
this.Model = new ThreadsModel();
Items = CollectionViewSource.GetDefaultView(Model.Threads);
this.StopThreadCommand = new StopThreadCommand(this);
}
public void InterruptThread(int id)
{
_model.InterruptThread(id);
}
}
StopThreadCommand:
public class StopThreadCommand : ICommand
{
public ThreadsViewModel ViewModel {get; set;}
public StopThreadCommand(ThreadsViewModel viewModel)
{
this.ViewModel = viewModel;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
this.ViewModel.InterruptThread((int)parameter);
}
}
When I am clicking on Stop button, IsInterrupted value is changing from false to true, and stackpanel have to become disabled, but UI does not update. Help please!

The default property of Binding is Path, which is a path to a property/sub-property of the DataContext. It's not an arbitrary C# expression. So you're setting Binding.Path to "!IsInterrupted". !IsInterrupted won't evaluate to the boolean inverse of IsInterrupted; it won't evaluate to anything. It'll get you this in the debug output stream:
System.Windows.Data Error: 40 : BindingExpression path error: '!IsInterrupted' property not found on 'object' 'ThreadDecorator' blah blah blah
<StackPanel
IsEnabled="{Binding !IsInterrupted, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
One way to do this is to write a boolean-inverse value converter (stolen verbatim from Chris Nicol's answer at the other end of that link):
[ValueConversion(typeof(bool), typeof(bool))]
public class InverseBooleanConverter: IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if (targetType != typeof(bool))
throw new InvalidOperationException("The target must be a boolean");
return !(bool)value;
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
#endregion
}
Usage:
<UserControl.Resources>
<local:InverseBooleanConverter x:Key="InverseBooleanConverter" />
</UserControl.Resources>
<!-- stuff etc. -->
IsEnabled="{Binding Path=IsReadOnly, Converter={StaticResource InverseBooleanConverter}}"
You could also write a Style with a DataTrigger that sets IsEnabled to False if IsInterrupted is true.

Related

UWP Binding to ConverterParameter not working

I am binding a VM property to my ConverterParemeter, but it always appear to be null in Converter, is there any alternative to pass property to the converter.
As I can't share the original code, below is the replica of the issue that I am facing. In my DummyConverter parameter is always null even though FilterType value is set.
Xaml:
<Grid>
<ComboBox x:Name="myComboBox" ItemsSource="{Binding ComboBoxList, Converter={StaticResource DummyConverter}, ConverterParameter={Binding FilterType}}" SelectedItem="{Binding SelectedPerson, Mode=TwoWay}" HorizontalAlignment="Center" VerticalAlignment="Center" Height="50" Width="150">
</ComboBox>
</Grid>
VM:
public class MainViewModel : INotifyPropertyChanged
{
private string header;
public string Header
{
get { return header; }
set
{
header = value;
RaisePropertyChange(nameof(Header));
}
}
private Person selectedPerson;
public Person SelectedPerson
{
get { return selectedPerson; }
set { selectedPerson = value; RaisePropertyChange(nameof(SelectedPerson)); }
}
private ObservableCollection<Person> comboBoxList;
public ObservableCollection<Person> ComboBoxList
{
get { return comboBoxList; }
set { comboBoxList = value; }
}
public FilterType FilterType { get; set; }
public DelegateCommand DropDownClosedCommand { get; set; }
public MainViewModel()
{
Header = "My Header";
FilterType = FilterType.None;
ComboBoxList = new ObservableCollection<Person> {
new Person() { Name = "Person 1", IsChecked = false },
new Person() { Name = "Person 2", IsChecked = false },
new Person() { Name = "Person 3", IsChecked = false },
new Person() { Name = "Person 4", IsChecked = false }
};
DropDownClosedCommand = new DelegateCommand(OnDropDownClosed);
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChange(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void OnDropDownClosed(object e)
{
//CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
//() =>
//{
SelectedPerson = ComboBoxList.FirstOrDefault(x => x.IsChecked);
//});
}
}
Converter:
public class DummyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return value;
}
}
In WPF, I can do with MultiBinding but in UWP MultiBinding is not available.
EDIT:
From this blog post I found out the reason of getting null values is "The reason why is that the ConverterParameter IS NOT a dependency property but a “simple” object."
So below is the modified code:
Xaml:
<Page.Resources>
<converters:DummyConverter x:Name="DummyConverter" FilterType="{Binding FilterType}"/>
</Page.Resources>
<Grid>
<ComboBox x:Name="myComboBox" ItemsSource="{Binding ComboBoxList, Converter={StaticResource DummyConverter}}" SelectedItem="{Binding SelectedPerson, Mode=TwoWay}" HorizontalAlignment="Center" VerticalAlignment="Center" Height="50" Width="150">
<interactivity:Interaction.Behaviors>
<core:DataTriggerBehavior Binding="{Binding IsDropDownOpen, ElementName=myComboBox}" ComparisonCondition="NotEqual" Value="True">
<core:InvokeCommandAction Command="{Binding DropDownClosedCommand}"/>
</core:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</ComboBox>
</Grid>
Converter:
public class DummyConverter : DependencyObject, IValueConverter
{
public FilterType FilterType
{
get { return (FilterType)GetValue(FilterTypeProperty); }
set { SetValue(FilterTypeProperty, value); }
}
public static readonly DependencyProperty FilterTypeProperty =
DependencyProperty.Register("FilterType",
typeof(FilterType),
typeof(DummyConverter),
new PropertyMetadata(null));
public object Convert(object value, Type targetType, object parameter, string language)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return value;
}
}
MainViewModel.cs
private void OnDropDownClosed(object e)
{
//CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
//() =>
//{
SelectedPerson = ComboBoxList.FirstOrDefault(x => x.IsChecked);
FilterType = FilterType.Descending;
this.RaisePropertyChange(nameof(ComboBoxList));
//});
}
I am changing the value of FilterType in OnDropDownClosed but it's not affecting in the converter.
I figured out the issue why FilterType was not changing, it's because PropertyChangedEvent was not firing. I updated the code as below and it's working now as expected.
private void OnDropDownClosed(object e)
{
SelectedPerson = ComboBoxList.FirstOrDefault(x => x.IsChecked);
FilterType = FilterType.Descending;
this.RaisePropertyChange(nameof(FilterType));
this.RaisePropertyChange(nameof(ComboBoxList));
}

How to avoid cumbersome code while binding form controls to classes hierarchy?

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.

How to update radio button in view from viewmodel?

Say I have a few radio buttons grouped together in my view.xaml:
<RadioButton GroupName="Group" Content="Item1" Command="{Binding ChangeRadioSelectionCommand}" CommandParameter="Item1" />
<RadioButton GroupName="Group" Content="Item2" Command="{Binding ChangeRadioSelectionCommand}" CommandParameter="Item2" />
<RadioButton GroupName="Group" Content="Item3" Command="{Binding ChangeRadioSelectionCommand}" CommandParameter="Item3" />
Then in my viewmodel.cs I have something like:
public class ViewModel : BindableBase
{
private string radioSelection = "Item1";
public string RadioSelection
{
get { return this.radioSelection; }
set { SetProperty(ref this.radioSelection, value); }
}
public ViewModel()
{
this.ChangeRadioSelectionCommand = new DelegateCommand<string>(this.OnChangeRadioSelection, this.CanChangeRadioSelection);
}
public ICommand ChangeRadioSelectionCommand { get; private set; }
private void OnChangeRadioSelection(string radioSelection)
{
RadioSelection = radioSelection;
}
private bool CanChangeRadioSelection(string radioSelection) { return true; }
}
This works fine for getting values from the view into the viewmodel, but how would I go from the viewmodel to the view if something changes in the viewmodel. For simplicity, let's say I add a button to the xaml:
<Button Command="{Binding ResetRadioSelectionCommand}" />
All it would do is reset the radio selection to the first item and so the viewmodel.cs would look something like:
public class ViewModel : BindableBase
{
private string radioSelection = "Item1";
public string RadioSelection
{
get { return this.radioSelection; }
set { SetProperty(ref this.radioSelection, value); }
}
public ViewModel()
{
this.ChangeRadioSelectionCommand = new DelegateCommand<string>(this.OnChangeRadioSelection, this.CanChangeRadioSelection);
this.ResetRadioSelectionCommand = new DelegateCommand(this.OnResetRadioSelection, this.CanResetRadioSelection);
}
public ICommand ChangeRadioSelectionCommand { get; private set; }
private void OnChangeRadioSelection(string radioSelection)
{
RadioSelection = radioSelection;
}
private bool CanChangeRadioSelection(string radioSelection) { return true; }
public ICommand ResetRadioSelectionCommand { get; private set; }
private void OnResetRadioSelection()
{
RadioSelection = "Item1";
}
private bool CanResetRadioSelection() { return true; }
}
This would change radioSelection, but it won't reflect in the gui. Is there a way to do this? Or perhaps just a better way to deal with radio buttons in general?
It is completely the wrong way. Your ViewModel should contain a sensible property with sensible name. For example, CurrentMode.
FIRST SOLUTION
ViewModel
public enum DisplayMode { Vertical, Horizontal, Diagonal }
private DisplayMode currentMode;
public DisplayMode CurrentMode
{
get { return currentMode; }
set { SetProperty(ref currentMode, value); }
}
And now you can bind this property to RadioButton.IsChecked via IValueConverter:
<RadioButton GroupName="Group" Content="Vertical" IsChecked="{Binding CurrentMode, Converter={StaticResource enumToBoolConverter}, ConverterParameter=Vertical}" />
<RadioButton GroupName="Group" Content="Horizontal" IsChecked="{Binding CurrentMode, Converter={StaticResource enumToBoolConverter}, ConverterParameter=Horizontal}" />
<RadioButton GroupName="Group" Content="Diagonal" IsChecked="{Binding CurrentMode, Converter={StaticResource enumToBoolConverter}, ConverterParameter=Diagonal}" />
Converter is generic for all enums. You need to add it to your project and declare in resource-block of your view.
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
}
It's one of many solutions. You may not want to us enum for your property because the subject area is not mapped to enumeration of parameters. Then you can bind to text value:
SECOND SOLUTION
ViewModel
private string currentMode;
public string CurrentMode
{
get { return currentMode; }
set { SetProperty(ref currentMode, value); }
}
View
<RadioButton Name="RadioButton1"
GroupName="Group"
Content="Vertical"
IsChecked="{Binding Path=CurrentMode, Converter={StaticResource boolToStringValueConverter}, ConverterParameter=Vertical}" />
<RadioButton Name="RadioButton2"
GroupName="Group"
Content="Horizontal"
IsChecked="{Binding Path=CurrentMode, Converter={StaticResource boolToStringValueConverter}, ConverterParameter=Horizontal}" />
<RadioButton Name="RadioButton3"
GroupName="Group"
Content="Diagonal"
IsChecked="{Binding Path=CurrentMode, Converter={StaticResource boolToStringValueConverter}, ConverterParameter=Diagonal}" />
Converter
public class BooleanToStringValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (System.Convert.ToString(value).Equals(System.Convert.ToString(parameter)))
{
return true;
}
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (System.Convert.ToBoolean(value))
{
return parameter;
}
return null;
}
}
The general principle is to store meaningful projection of the subject area in ViewModels. There is no many sense if you'll keep store copy of view properties in your ViewModel. RadioSelection is a senseless name and it can't be correlated to model without additional commentaries.

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

Combobox selected item doesn't show up On Load

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 :)

Categories

Resources