UWP Binding to ConverterParameter not working - c#

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

Related

How do I use x:Bind function binding with a null parameter?

I am trying to format several properties of an object and bind the result to a TextBlock using x:Bind function binding. The binding looks like this:
<TextBlock Text="{x:Bind local:MainViewModel.FormatWidget(ViewModel.SelectedItem), Mode=OneWay}" />
As long as the object is not null, this works perfectly. However, when the object is null, my function is not called. Or to be more precise, if the object is null initially, the function is called, but if the object changes to null later, the function is not called.
Why is the function not being called when the parameter is null and how can I use it for this case?
Here's a repro. When you run it, notice that initially the function binds correctly to the null SelectedItem and displays "No widget selected." But when you select an item and then unselect it (CTRL + click to unselect), it does not call the function and displays the FallbackValue. (If the FallbackValue is not set, it does not update the binding at all.)
MainPage.xaml
<Page
x:Class="NullFunctionBindingParameter.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:NullFunctionBindingParameter">
<Page.Resources>
<Style TargetType="TextBlock">
<Setter Property="Margin" Value="20" />
</Style>
</Page.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ListView
Grid.Column="0"
ItemsSource="{x:Bind ViewModel.Widgets, Mode=OneWay}"
SelectedItem="{x:Bind ViewModel.SelectedItem, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Widget">
<TextBlock Text="{x:Bind Name}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<TextBlock Grid.Column="1" Text="{x:Bind local:MainViewModel.FormatWidget(ViewModel.SelectedItem), Mode=OneWay, FallbackValue=MyFallbackValue}" />
</Grid>
</Page>
MainPage.xaml.cs
using Windows.UI.Xaml.Controls;
namespace NullFunctionBindingParameter
{
public sealed partial class MainPage : Page
{
public MainPage()
{
InitializeComponent();
}
public MainViewModel ViewModel { get; } = new MainViewModel();
}
}
MainViewModel.cs
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace NullFunctionBindingParameter
{
public class MainViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private Widget _selectedItem;
public Widget SelectedItem
{
get => _selectedItem;
set
{
if (_selectedItem != value)
{
_selectedItem = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedItem)));
}
}
}
public ObservableCollection<Widget> Widgets { get; } = new ObservableCollection<Widget>()
{
new Widget
{
Id = Guid.NewGuid(),
Name = "Regular Widget",
Model = "WX2020-01",
Description = "Your typical everyday widget."
},
new Widget
{
Id = Guid.NewGuid(),
Name = "Super Widget",
Model = "WX2020-02",
Description = "An extra special upgraded widget."
},
new Widget
{
Id = Guid.NewGuid(),
Name = "Broken Widget",
Model = "WX2020-03",
Description = "A widget that has been used and abused."
},
new Widget
{
Id = Guid.NewGuid(),
Name = "Fake Widget",
Model = "WX2020-04",
Description = "It's not really a widget at all!"
},
new Widget
{
Id = Guid.NewGuid(),
Name = "Surprise Widget",
Model = "WX2020-05",
Description = "What kind of widget will it be?"
},
new Widget
{
Id = Guid.NewGuid(),
Name = "Invisible Widget",
Model = "WX2020-06",
Description = "Our most inexpensive widget."
},
new Widget
{
Id = Guid.NewGuid(),
Name = "Backwards Widget",
Model = "WX2020-07",
Description = "Really more of a tegdiw, come to think of it."
}
};
public static string FormatWidget(Widget widget)
{
if (widget == null)
return "No widget selected";
else
return $"{widget.Name} [{widget.Model}] {widget.Description}";
}
public string GetFormattedWidget()
{
return FormatWidget(SelectedItem);
}
}
}
Widget.cs
using System;
using System.ComponentModel;
namespace NullFunctionBindingParameter
{
public class Widget : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private Guid _id;
private string _name;
private string _model;
private string _description;
public Guid Id
{
get => _id;
set
{
if (_id != value)
{
_id = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Id)));
}
}
}
public string Name
{
get => _name;
set
{
if (_name != value)
{
_name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
}
}
}
public string Model
{
get => _model;
set
{
if (_model != value)
{
_model = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Model)));
}
}
}
public string Description
{
get => _description;
set
{
if (_description != value)
{
_description = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Description)));
}
}
}
}
}
In this case, I recommend that you use Converter instead of using static methods directly in the binding statement.
WidgetConverter
public class WidgetConverter:IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var widget = value as Widget;
return MainViewModel.FormatWidget(widget);
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
Usage
<Page.Resources>
...
<local:WidgetConverter x:Key="WidgetConverter"/>
</Page.Resources>
...
<TextBlock
Grid.Row="2"
Grid.Column="2"
Text="{x:Bind ViewModel.SelectedItem, Mode=OneWay,Converter={StaticResource WidgetConverter}}"/>
Best regards.

Binding the same Collection to ComboBox and Datagrid

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.

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.

WPF: two way data binding is not working

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.

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

Categories

Resources