My WPF-Application contains a DataGrid with 3 Columns and a disabled Button.
One Column of my DataGrid is a DataGridCheckBoxColumn. Now I want to bind Button's IsEnabled-Property, to be enable if at least one of them is checked, else remain disabled.
Actually I've following implementation:
<Button x:Name="buttonStart" Content="Start" IsEnabled="{Binding Converter={StaticResource ButtonEnableConverter}, ElementName=gridTestCaseChooser}" />
But the Converter is only called once at startup the application. If the DataGrid content change, or I check/uncheck a CheckBox, there isn't a new call to my converter.
How could I do this?
Using MVVM can be very easy:
Declare an ICommand property to execute the logic of the button with a RelayCommand.
The items you add must implement INotifyPropertyChanged interface and have a bool IsChecked property firing PropertyChanged event.
Every time a new item is added, subscribe to its PropertyChanged event and when removed, remove the hook as well.
Every time the hooking method is called, fire the PropertyChanged for the ICommand property.
In XAML just bind the button to the ICommand property.
ViewModels
public class Item: INotifyPropertyChanged
{
private bool isChecked;
public event PropertyChangedEventHandler PropertyChanged;
public bool IsChecked
{
get
{
return this.isChecked;
}
set
{
this.isChecked = value;
var propertyChangedEvent = PropertyChanged;
if(propertyChangedEvent != null)
{
propertyChangedEvent(this, new PropertyChangedEventArgs("UpdateSomething");
}
}
}
}
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ICommand UpdateSomething { get; private set; }
public ObservableCollection<Item> MyItems { get; set; }
public MyViewModel()
{
UpdateSomething = new RelayCommand(MyCommandExecute, MyCommandCanExecute);
MyItems = new ObservableCollection<Item>();
}
private void MyCommandExecute(object parameter)
{
// Your logic here.
}
private void MyCommandCanExecute(object parameter)
{
return MyItems.Any(item => item.IsChecked);
}
private void AddItem(Item item)
{
item.PropertyChanged += ItemsPropertyChanged;
MyItems.Add(item);
}
private void ItemsPropertyChanged(object sender, PropertyChangedEventArgs e)
{
var propertyChangedEvent = PropertyChanged;
if(propertyChangedEvent != null &&
e.PropertyName == "IsChecked")
{
propertyChangedEvent(this, new PropertyChangedEventArgs("UpdateSomething");
}
}
You need to bind not to visual element like grid, but rather to some DependencyProperty, which value is changing. Grid itself is not changing usually, that's why converter is only called one time on startup. I would create a UserControl, which has the grid with check-boxes and the button. That UserControl would define a DependencyProperty of type bool, which would be set to true or false when one of the check-boxes is checked or unchecked. Then IsEnabled property of Button would bind to that DependencyProperty.
for your button:
IsEnabled="{Binding ElementName=NN, Path=IsChecked}">
Update after question was updated
the same situation you have is discussed here:
What causes a Value Converter to fire?
The idea is to choose something that is really change its own state
Updated
try something like this:
<dg:DataGrid.Columns>
<dg:DataGridTextColumn Header="First Name" Width="50">
<dg:DataGridTextColumn.DataFieldBinding>
<Binding Path="FirstName" />
</dg:DataGridTextColumn.DataFieldBinding>
</dg:DataGridTextColumn>
<dg:DataGridCheckBoxColumn Header="Active" Width="50" DataFieldBinding="{Binding Path= IsActive}" />
</dg:DataGrid.Columns>
Related
I am on a MVVM C# project.
I want to display a list of objects.
I want to add and remove items in this list and ALSO change items in this list.
So I choosed the BindingList<> over the ObservableCollection<>, which would not get noticed if an item has changed.
(I also tested the ObservableCollectionEx which is out there in the web, but this has the same behavior like the BindingList for me).
But the Listbox is not changing when items are changed.
(Adding and removing items is updated in the Listbox)
In my XAML
<ListBox DisplayMemberPath="NameIndex" ItemsSource="{Binding Profiles}" SelectedItem="{Binding SelectedProfile}">
or alternative with the ItemTemplate
<ListBox DockPanel.Dock="Right" ItemsSource="{Binding Profiles}" SelectedItem="{Binding SelectedProfile}" Margin="0,10,0,0">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding NameIndex}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In my ViewModel (ViewModelBase is implementing INotifyPropertyChanged etc)
public class ProfileListViewModel : ViewModelBase
{
private BindingList<Profile> profiles;
public BindingList<Profile> Profiles
{
get
{
return profiles;
}
set
{
profiles = value;
RaisePropertyChanged();
}
}
My items are also implementing INotifyPropertyChanged and I am calling OnPropertyChanged("Name") in my Setters.
My model
public class Profile : INotifyPropertyChanged
{
public Profile(){}
public int ProfileID { get; set; }
private string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged("Name");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Wiring the View with the ViewModel (BindingList is initialized before View)
ProfileListViewModel plvw= new ProfileListViewModel(message.Content);
var profileView = new ProfileListView(plvw);
profileView.ShowDialog();
In the View.xaml.cs
public ProfileListView(ProfileListViewModel plvw)
{
InitializeComponent();
DataContext = plvw;
}
When I am changing the name of an object then I get the ListChanged event to which I have subscribted in my ViewModel (Profiles.ListChanged += Profiles_ListChanged;) for testing BUT the items in the ListBox are NOT changing.
What am I doing wrong?
How can I get a updated Listbox?
Since your DisplayIndex is the computed property NameIndex, you need to call OnPropertyChanged("NameIndex") when its value changes due to a change in other properties, e.g.:
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged("Name");
OnPropertyChanged("NameIndex");
}
}
Use
Profiles.ResetBindings() to bind it again.
I have an issue whereby when I update the text on a Comboboxitem it isnt instantly reflected on the UI. One has to click the Combobox to show the items (which have the correct text). Any ideas why? Note this exact code works perfectly in WPF
The property that defines the string to show
public string NormallyOpenString
{
get
{
if (this.IsInput)
{
return "High";
}
else if (this.IsRelay)
{
return "Open";
}
else
{
return "Open (High)";
}
}
}
Which is bound to a Combobox like so
<ComboBox SelectedIndex="{Binding Normally, Mode=TwoWay}" >
<ComboBoxItem Content="{Binding NormallyOpenString}" />
<ComboBoxItem Content="{Binding NormallyClosedString}" />
</ComboBox>
When another combobox changes I want to update the text as it changes what IsInput / IsRelay is. I do this via NotifyPropertyChanged like so
this.NotifyPropertyChanged("NormallyOpenString");
this.NotifyPropertyChanged("NormallyClosedOpenString");
this.NotifyPropertyChanged("Normally");
I've never done it that way, so I can't vouch for it. This is how I do property change notification:
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
private string normallyOpenString = "I'm an open string!";
public string NormallyOpenString
{
get { return normallyOpenString; }
set
{
normallyOpenString = value;
RaisePropertyChanged("NormallyOpenString");
}
}
}
So now, whenever anyone calls your setter, anything bound to your property will be updated. So if it gets set from one binding, all other bindings that are also bound to it will be updated.
I think you should rather use the SelectedItem property.
I'm using MVVM, VS 2008, and .NET 3.5 SP1. I have a list of items, each exposing an IsSelected property. I have added a CheckBox to manage the selection/de-selection of all the items in the list (updating each item's IsSelected property). Everything is working except the IsChecked property is not being updated in the view when the PropertyChanged event fires for the CheckBox's bound control.
<CheckBox
Command="{Binding SelectAllCommand}"
IsChecked="{Binding Path=AreAllSelected, Mode=OneWay}"
Content="Select/deselect all identified duplicates"
IsThreeState="True" />
My VM:
public class MainViewModel : BaseViewModel
{
public MainViewModel(ListViewModel listVM)
{
ListVM = listVM;
ListVM.PropertyChanged += OnListVmChanged;
}
public ListViewModel ListVM { get; private set; }
public ICommand SelectAllCommand { get { return ListVM.SelectAllCommand; } }
public bool? AreAllSelected
{
get
{
if (ListVM == null)
return false;
return ListVM.AreAllSelected;
}
}
private void OnListVmChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "AreAllSelected")
OnPropertyChanged("AreAllSelected");
}
}
I'm not showing the implementation of SelectAllCommand or individual item selection here, but it doesn't seem to be relevant. When the user selects a single item in the list (or clicks the problem CheckBox to select/de-select all items), I have verified that the OnPropertyChanged("AreAllSelected") line of code executes, and tracing in the debugger, can see the PropertyChanged event is subscribed to and does fire as expected. But the AreAllSelected property's get is only executed once - when the view is actually rendered. Visual Studio's Output window does not report any data binding errors, so from what I can tell, the CheckBox's IsSelected property is properly bound.
If I replace the CheckBox with a Button:
<Button Content="{Binding SelectAllText}" Command="{Binding SelectAllCommand}"/>
and update the VM:
...
public string SelectAllText
{
get
{
var msg = "Select All";
if (ListVM != null && ListVM.AreAllSelected != null && ListVM.AreAllSelected.Value)
msg = "Deselect All";
return msg;
}
}
...
private void OnListVmChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "AreAllSelected")
OnPropertyChanged("SelectAllText");
}
everything works as expected - the button's text is updated as all items are selected/desected. Is there something I'm missing about the Binding on the CheckBox's IsSelected property?
Thanks for any help!
I found the problem. It seems a bug existed in WPF 3.0 with OneWay bindings on IsChecked causing the binding to be removed. Thanks to this post for the assistance, it sounds like the bug was fixed in WPF 4.0
To reproduce, create a new WPF project.
Add a FooViewModel.cs:
using System;
using System.ComponentModel;
using System.Windows.Input;
namespace Foo
{
public class FooViewModel : INotifyPropertyChanged
{
private bool? _isCheckedState = true;
public FooViewModel()
{
ChangeStateCommand = new MyCmd(ChangeState);
}
public bool? IsCheckedState
{
get { return _isCheckedState; }
}
public ICommand ChangeStateCommand { get; private set; }
private void ChangeState()
{
switch (_isCheckedState)
{
case null:
_isCheckedState = true;
break;
default:
_isCheckedState = null;
break;
}
OnPropertyChanged("IsCheckedState");
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var changed = PropertyChanged;
if (changed != null)
changed(this, new PropertyChangedEventArgs(propertyName));
}
}
public class MyCmd : ICommand
{
private readonly Action _execute;
public event EventHandler CanExecuteChanged;
public MyCmd(Action execute)
{
_execute = execute;
}
public void Execute(object parameter)
{
_execute();
}
public bool CanExecute(object parameter)
{
return true;
}
}
}
Modify Window1.xaml.cs:
using System.Windows;
using System.Windows.Controls.Primitives;
namespace Foo
{
public partial class Window1
{
public Window1()
{
InitializeComponent();
}
private void OnClick(object sender, RoutedEventArgs e)
{
var bindingExpression = MyCheckBox.GetBindingExpression(ToggleButton.IsCheckedProperty);
if (bindingExpression == null)
MessageBox.Show("IsChecked property is not bound!");
}
}
}
Modify Window1.xaml:
<Window
x:Class="Foo.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:Foo"
Title="Window1"
Height="200"
Width="200"
>
<Window.DataContext>
<vm:FooViewModel />
</Window.DataContext>
<StackPanel>
<CheckBox
x:Name="MyCheckBox"
Command="{Binding ChangeStateCommand}"
IsChecked="{Binding Path=IsCheckedState, Mode=OneWay}"
Content="Foo"
IsThreeState="True"
Click="OnClick"/>
<Button Command="{Binding ChangeStateCommand}" Click="OnClick" Content="Change State"/>
</StackPanel>
</Window>
Click on the button a few times and see the CheckBox's state toggle between true and null (not false). But click on the CheckBox and you will see that the Binding is removed from the IsChecked property.
The workaround:
Update the IsChecked binding to be TwoWay and set its UpdateSourceTrigger to be explicit:
IsChecked="{Binding Path=IsCheckedState, Mode=TwoWay, UpdateSourceTrigger=Explicit}"
and update the bound property so it's no longer read-only:
public bool? IsCheckedState
{
get { return _isCheckedState; }
set { }
}
I got a sample mvvm app. The UI has a textbox, a button and a combobox. when I enter something in the textbox and hit the button, the text I enter gets added to an observablecollection. The Combobox is bound to that collection. How do I get the combobox to display the newly added string automaticly?
As I understand correctly, you want to add an item and select it.
Here is the example how it can be done using ViewModel and bindings.
Xaml:
<StackPanel>
<TextBox Text="{Binding ItemToAdd}"/>
<ComboBox ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}" />
<Button Content="Add" Click="Button_Click"/>
</StackPanel>
ViewModel:
public class MainViewModel:INotifyPropertyChanged
{
public ObservableCollection<string> Items { get; set; }
public string ItemToAdd { get; set; }
private string selectedItem;
public string SelectedItem
{
get { return selectedItem; }
set
{
selectedItem = value;
OnPropertyChanged("SelectedItem");
}
}
public void AddNewItem()
{
this.Items.Add(this.ItemToAdd);
this.SelectedItem = this.ItemToAdd;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The MainViewModel has 3 properties (one for the TextBox and two other for the ComboBox) and the method AddNewItem without parameters.
The method can be triggered from a command, but there is no standard class for commands, so I will call it from the code-behind:
((MainViewModel)this.DataContext).AddNewItem();
So you must explicitly set an added item as selected after you add it to a collection.
Because the method OnItemsChanged of the ComboBox class is protected and can't be used.
If the ComboBox is bound to an ObservableCollection, the ComboBox will be updated as soon as the collection is changed.
That's the advantage of using an ObservableCollection - you don't need to do any extra coding to update the UI.
If this is not the behavior you're seeing, perhaps you can post some code/xaml.
In WPF I have a collection of bool? values and I want to bind each of these to a separate checkbox programmatically. I want the bindings to be TwoWay so that changing the value of the individual item in the collection in code updates the check box and vice versa.
I have spent ages trying to figure out how to do this and I am completely stuck. With the following code the checkbox only gets the right value when the window is loaded and that's it. Changing the check box doesn't even update the value in the collection. (UPDATE: this appears to be a bug in .NET4 as the collection does get updated in an identical .NET3.5 project. UPDATE: Microsoft have confirmed the bug and that it will be fixed in the .NET4 release.)
Many thanks in advance for your help!
C#:
namespace MyNamespace
{
public partial class MyWindow : Window, INotifyPropertyChanged
{
public MyWindow()
{
InitializeComponent();
DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public List<bool?> myCollection = new List<bool?>
{ true, false, true, false, true, false };
public List<bool?> MyCollection
{
get { return myCollection; }
set { myCollection = value; }
}
}
}
XAML:
<CheckBox IsChecked="{Binding Path=MyCollection[0], Mode=TwoWay}">
There are a few things that need changing here to get this to work. Firstly you'll need to wrap your boolean value in an object that implements the INotifyPropertyChanged interface in order to get the change notification that you are looking for. Currently you are binding to boolean values in your collection which do not implement the interface. To do this you could create a wrapper class like so :
public class Wrapper: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private bool val = false;
public bool Val
{
get { return val; }
set
{
val = value;
this.OnPropertyChanged("Val");
}
}
public Wrapper(bool val)
{
this.val = val;
}
}
You'll then want to create these objects in your form instead of a list of booleans. You may also want to use an observable collection instead of a list so that notification of items being added and removed are sent. This is shown below:
public Window1()
{
InitializeComponent();
this.DataContext = this;
}
private ObservableCollection<Wrapper> myCollection = new ObservableCollection<Wrapper>()
{new Wrapper(true), new Wrapper(false), new Wrapper(true)};
public ObservableCollection<Wrapper> MyCollection
{
get { return myCollection; }
}
The next thing to do is to display a list of check boxes in your ui. To do this WPF provides itemscontrols. ListBox is an itemscontrol so we can use this as a starting point. Set the itemssource of a listbox to be MyCollection. We then need to define how each Wrapper object is going to be displayed in the list box and this can be done with a datatemplate which is created in the windows resources. This is shown below :
<Window.Resources>
<DataTemplate x:Key="myCollectionItems">
<CheckBox IsChecked="{Binding Path=Val, Mode=TwoWay}"></CheckBox>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding Path=MyCollection}" ItemTemplate="{StaticResource myCollectionItems}"></ListBox>
</Grid>
This should get you up and running with a simple demo of checkboxes that have values bound to a list of booleans.
What makes you think it's not working? It's working for me :)
Here's my test XAML:
<UniformGrid>
<CheckBox IsChecked="{Binding Path=MyCollection[0], Mode=TwoWay}"/>
<ListBox ItemsSource="{Binding MyCollection}"/>
<Button Content="Test" Click="Button_Click"/>
</UniformGrid>
Here's my code behind:
private void Button_Click(object sender, RoutedEventArgs e)
{
}
(the rest is the same as yours)
I placed a breakpoint on Button_Click and checked MyCollection[0] it was updated according to the IsChecked value of the CheckBox.
Try changing your collection type from List<bool?> to ObservableCollection<bool?> perhaps that is the reason you think it's not working for you (the fact that changes to the collection are not reflected anywhere else in your view).
Change your List<bool?> to an ObservableCollection<bool?>. A List does not raise the change notifications that WPF needs to update the UI. An ObservableCollection does. This handles the case where the list entry is changed and the CheckBox needs to update accordingly.
In the other direction, it works for me even with a List<bool?> -- i.e. toggling the checkbox modifies the value in the collection. Your binding syntax is certainly correct.