After changing CheckBox.IsChecked by DataTrigger source value to which CheckBox.IsChecked has binding is not changing.
I have simple ViewModel
public class ViewModel : INotifyPropertyChanged
{
private bool check1;
public bool Check1
{
get { return check1; }
set { check1 = value; NotifyPropertyChanged(); }
}
private bool check2;
public bool Check2
{
get { return check2; }
set { check2 = value; NotifyPropertyChanged(); }
}
#region Notify
...
#endregion
}
and simple XAML
<StackPanel Grid.Row="1">
<CheckBox Content="Check1">
<CheckBox.Style >
<Style TargetType="{x:Type CheckBox}">
<Setter Property="IsChecked" Value="{Binding Check1, Mode=TwoWay}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Check2}" Value="True">
<Setter Property="IsChecked" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
</CheckBox>
<CheckBox Content="Check2" IsChecked="{Binding Check2}"/>
<TextBlock Text="{Binding Check1}" Name="uiCheckValue1"/>
<TextBlock Text="{Binding Check2}" Name="uiCheckValue2"/>
</StackPanel>
When I check CheckBox2 the CheckBox1 becomes checked but source is not updated. How to make it update source?
Your code seems to be wrong. You should emit the PropertyChanged event properly to update the view.
Check below code :
public class ViewModel : INotifyPropertyChanged
{
private bool check1;
public bool Check1
{
get { return check1; }
set
{
check1 = value;
PropertyChanged(value, new PropertyChangedEventArgs("Check1"));
}
}
private bool check2;
public bool Check2
{
get { return check2; }
set
{
check2 = value;
PropertyChanged(value, new PropertyChangedEventArgs("Check1"));
}
}
#region Notify
...
#endregion
}
Also change the binding to TwoWay
<<CheckBox Content="Check2" IsChecked="{Binding Check2, Mode=TwoWay}"/>
<DataTrigger Binding="{Binding Check2, Mode=TwoWay}" Value="True">
<Setter Property="IsChecked" Value="True" />
</DataTrigger>
It's easy to say why: You loose the binding if you set IsChecked on True manually.
Delete the DataTrigger and change your ViewModel like this:
public bool Check2{
get { return check2; }
set {
check2 = value;
if (value == true) Check1 = true;
NotifyPropertyChanged();
}
}
A little late, but if you Change
<CheckBox Content="Check2" IsChecked="{Binding Check2}"/>
To
<CheckBox Content="Check2" IsChecked="{Binding Check2, UpdateSourceTrigger=PropertyChanged}"/>
you can get rid of all the code behind. The default for checkboxes is something like LostFocus, which is entirely annoying.
Related
How do I make a Button show if a certain value is selected in a ComboBox using XAML ?
This is what I have tried.
Thanks
<ComboBox x:Name="ComboBox" Margin="171,102,426,271">
<ComboBoxItem>Testing</ComboBoxItem>
<ComboBoxItem>Again</ComboBoxItem>
<ComboBoxItem>Finally</ComboBoxItem>
</ComboBox>
<Button Margin="10, 0, 0, 0" >
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedIndex, ElementName=ComboBox}" Value="Testing">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
A better approach is to bind the controls to a view model and to integrate the logic there. See: Explain Combo Box Binding In MVVM - WPF.
As an example we create a window for editing of person data. It contains a combobox where the user can select a city. When a certain city is selected, a button is is displayed, otherwise it is hidden.
You could have a view model looking like this
public class PersonViewModel: INotifyPropertyChanged
{
private string _city;
public string City
{
get { return _city; }
set {
if (value != _city) {
_city = value;
OnPropertyChanged(nameof(City));
OnPropertyChanged(nameof(MyButtonVisibility));
}
}
}
public List<string> Cities { get; } = new List<string> { "Austin", "Boston", "Chicago"};
public Visibility MyButtonVisibility => City == "Boston"
? Visibility.Visible
: Visibility.Hidden;
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
// Other properties
private string _firstName;
public string FirstName
{
get { return _firstName; }
set {
if (value != _firstName) {
_firstName = value;
OnPropertyChanged(nameof(FirstName));
}
}
}
private string _lastName;
public string LastName
{
get { return _lastName; }
set {
if (value != _lastName) {
_lastName = value;
OnPropertyChanged(nameof(LastName));
}
}
}
}
Note that it implements INotifyPropertyChanged. It has a Cities collection used to display the combobox items and a City property for the selected city.
We also need a property for the button visibility (MyButtonVisibility). Note that when the selected city changes, we also raise the PropertyChanged event for MyButtonVisibility to tell WPF to requery the button visibility.
In the window's constructor we assign the view model:
public MainWindow()
{
InitializeComponent();
DataContext = new PersonViewModel();
}
The XAML code for the combobox is
<ComboBox x:Name="citiesComboBox" HorizontalAlignment="Left" Margin="116,96,0,0"
VerticalAlignment="Top" Width="120"
ItemsSource="{Binding Path=Cities}"
SelectedItem="{Binding Path=City}"
/>
The XAML code for the button is
<Button Content="Button" HorizontalAlignment="Left" Margin="116,164,0,0"
VerticalAlignment="Top" Width="75"
Visibility="{Binding MyButtonVisibility}"
/>
By the magic of WPF binding, now the button appears or disappears automatically, when you select cities.
The binding path should be SelectedItem.Content for your trigger to work:
<Button Margin="10, 0, 0, 0" >
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedItem.Content, ElementName=ComboBox}" Value="Testing">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
You are currently binding to the SelectedIndex property and this one never has a value of "Testing". The currently selected ComboBoxItem's Content property may have though.
If you want to show the Button when the "Testing" option is selected, you should also modify the value fo your setter:
<Button Margin="10, 0, 0, 0" >
<Button.Style>
<Style TargetType="Button">
<Setter Property="Visibility" Value="Hidden"/>
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedItem.Content, ElementName=ComboBox}" Value="Testing">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
My bindable property RevalSurfaceChecked do not get updated when we set IsChecked property in trigger
<CheckBox Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="2" x:Name="chkRevalSurface" Content="Export Reval Surface (if applicable)"
HorizontalAlignment="Left" VerticalAlignment="Center">
<CheckBox.IsEnabled>
<MultiBinding Converter="{StaticResource RevalSurfaceCheckboxEnableConverter}">
<Binding ElementName="ChkExportToCSV" Path="IsChecked"></Binding>
<Binding ElementName="chkExportToExcel" Path="IsChecked"></Binding>
</MultiBinding>
</CheckBox.IsEnabled>
<CheckBox.Style>
<Style TargetType="{x:Type CheckBox}">
<Setter Property="IsChecked" Value="{Binding RevalSurfaceChecked}" />
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="IsChecked" Value="False"></Setter>
</Trigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
</CheckBox>
The logic here is that if I check any of the two checkbox, then the chkRevalSurface should get enabled else stay disabled-written in converter. When chkRevalSurface is disabled it should be set unchecked (written in trigger) and RevalSurfaceChecked property should be set to False. if checked set it to True in viewmodel.
ButRevalSurfaceCheckedis not set to true or false.
The problem is that the <Setter Property="IsChecked" Value="False"></Setter> clears the original binding, so your CheckBox's IsChecked property just ends up being False without changing the underlying property.
You mentioned you have a viewmodel, I assume the ChkExportToCSV and chkExportToExcel CheckBoxes are bound to a property in your viewmodel.
Lets assume this viewmodel:
public class MainWindowViewModel : INotifyPropertyChanged
{
private bool _revalSurfaceChecked;
public bool RevalSurfaceChecked
{
get { return _revalSurfaceChecked; }
set
{
_revalSurfaceChecked = value;
OnPropertyChanged();
}
}
private bool _isChkExportToCSVChecked;
public bool IsChkExportToCSVChecked
{
get { return _isChkExportToCSVChecked; }
set
{
_isChkExportToCSVChecked = value;
RevalSurfaceChecked = RevalSurfaceChecked && (_isChkExportToCSVChecked || _ischkExportToExcelChecked);
OnPropertyChanged();
}
}
private bool _ischkExportToExcelChecked;
public bool IschkExportToExcelChecked
{
get { return _ischkExportToExcelChecked; }
set
{
_ischkExportToExcelChecked = value;
RevalSurfaceChecked = RevalSurfaceChecked && (_isChkExportToCSVChecked || _ischkExportToExcelChecked);
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
{
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And this XAML:
<CheckBox x:Name="ChkExportToCSV" Content="ChkExportToCSV" IsChecked="{Binding IsChkExportToCSVChecked}"
HorizontalAlignment="Left" VerticalAlignment="Center" />
<CheckBox x:Name="chkExportToExcel" Content="chkExportToExcel" IsChecked="{Binding IschkExportToExcelChecked}"
HorizontalAlignment="Left" VerticalAlignment="Center" />
<CheckBox x:Name="chkRevalSurface" Content="Export Reval Surface (if applicable)"
HorizontalAlignment="Left" VerticalAlignment="Center"
IsChecked="{Binding RevalSurfaceChecked}">
<CheckBox.IsEnabled>
<MultiBinding Converter="{StaticResource AnyTrueConverter}">
<Binding ElementName="ChkExportToCSV" Path="IsChecked"></Binding>
<Binding ElementName="chkExportToExcel" Path="IsChecked"></Binding>
</MultiBinding>
</CheckBox.IsEnabled>
</CheckBox>
I'm binding the ChkExportToCSV's IsChecked property to the IsChkExportToCSVChecked property in the viewmodel and the chkExportToExcel's IsChecked to the IschkExportToExcelChecked property.
I removed the Style from chkRevalSurface and for the IsEnabled I use a converter that gives back true if any of the inputs are true. I assume your RevalSurfaceCheckboxEnableConverter does the same, but I'll include it here:
public class AnyTrueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values != null && values.OfType<bool>().Any(b => b);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
The trick is in the setter of IsChkExportToCSVChecked and IschkExportToExcelChecked:
RevalSurfaceChecked = RevalSurfaceChecked && (_isChkExportToCSVChecked || _ischkExportToExcelChecked);
We moved the logic from the DataTrigger to here. This should solve your problem. I tested and it works for me.
I have a class derived from INotifyPropertyChanged to track property changes. This properties are bound to a TreeView and are needed to perform searching in TreeViewItems. When in properties setters this.OnPropertyChanged("IsExpanded") is called for some reason this.PropertyChanged == null when "IsExpanded" bubbled to the root element of the TreeView. Why is it null here and not null on deeper TreeView elements? What can I to solve this issue?
My code cs:
public class TreeViewItemViewModel:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
....................
bool _isExpanded;
public bool IsExpanded
{
get { return _isExpanded; }
set
{
if (value != _isExpanded)
{
_isExpanded = value;
this.OnPropertyChanged("IsExpanded");
}
//Expand all till the root
if (_isExpanded && _parent != null)
_parent._isExpanded = true;
//Lazy load children, if nessesary
if (this.HasDummyChild)
{
this.Children.Remove(DummyChild);
this.LoadChildren();
}
}
}
bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
if (value != _isSelected)
{
_isSelected = value;
this.OnPropertyChanged("IsSelected");
}
}
}
.............................
}
My code XAML:
<TreeView ItemsSource="{Binding Areas}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="FontWeight" Value="Normal" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate
DataType="{x:Type vAreas:AreaViewModel}"
ItemsSource="{Binding Children}"
>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
The problem was that I never subscribe for my event PropertyChanged.
As soon as I subscribe for it it start to work fine
this.PropertyChanged += AreaViewModel_PropertyChanged;
void AreaViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsExpanded")
{
//Expand all till the root
if (this.IsExpanded && this.Parent != null)
this.Parent.IsExpanded = true;
//Lazy load children, if nessesary
if (this.HasDummyChild)
{
this.Children.Remove(DummyChild);
this.LoadChildren();
}
}
}
I struggled with this one for a while. In the end it turned out that my collection feeding the treeview was an ObservableCollection property, but was actually a list.
As soon as I turned it into an ObservableCollection everything started working.
I am trying to create a templated button control with databinding for the Visibility, tooltip, and Command. The Visibility binding works, as does the tooltip, but the Command does not. Another process is responsible for injecting the viewmodel and associating it with the View, and the other data bindings are working so I am pretty confident that is working properly.
In the resource dictionary:
<Converters:BoolToVisibilityConverter x:Key="boolVisibilityConverter" />
<Style TargetType="local:ImageButton">
<Setter Property="Visibility" Value="{Binding FallbackValue=Visible, Path=ToolIsAvailable, Converter={StaticResource boolVisibilityConverter} }"/>
<Setter Property="Command" Value="{Binding ButtonCommand}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:ImageButton">
<Grid>
<Image Source="{TemplateBinding Image}"
ToolTipService.ToolTip="{Binding ToolName}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
the templated control
public class MyButton: ImageButton
{
public MyButton(MyCommandViewModel viewmodel)
{
this.DefaultStyleKey = typeof(ImageButton);
this.Image = new BitmapImage(new Uri("/MyProject;component/Themes/myimage.png", UriKind.Relative));
this.DataContext = viewmodel;
}
}
and in the view model
public MyCommandViewModel()
: base("My Tool", true)
{
}
public class CommandViewModel
{
public CommandViewModel(string toolName, bool isAvailable)
{
ToolIsAvailable = isAvailable;
ToolName = toolName;
_buttoncommand = new DelegateCommand(() =>
{
ExecuteCommand();
},
() => { return CanExecute; });
}
private bool _canExecute = true;
public bool CanExecute
{
get { return _canExecute; }
set
{
_canExecute = value;
OnPropertyChanged("CanExecute");
if (_command != null) _command.RaiseCanExecuteChanged();
}
}
private DelegateCommand _buttoncommand;
public ICommand ButtonCommand
{
get { return _buttoncommand; }
}
protected virtual void ExecuteCommand()
{
}
public bool ToolIsAvailable
{
get { return _toolIsReady; }
set { _toolIsReady = value; OnPropertyChanged("ToolIsAvailable"); }
}
public string ToolName
{
get { return _toolName; }
set { _toolName = value; OnPropertyChanged("ToolName"); }
}
}
Why are the other databindings functioning properly but not the Command data binding. I found this similar post
Overriding a templated Button's Command in WPF
Do I need to template a grid control instead and use RoutedCommands? I am not sure I understand why Silverlight treats the Command binding different than the others so I suspect I just have a bug in the code.
Does specifically looking for the datacontext work?
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.ButtonCommand}"
This was my solution. Using the same commandviewmodel as above and same MyCommandViewModel
<Style TargetType="local:ImageButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:ImageButton">
<Grid>
<Image Source="{TemplateBinding Image}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The databinding is now done in a user control
<UserControl x:Class="SilverlightApplication11.Test"
...
>
<UserControl.Resources>
<Converters:BoolToVisibilityConverter x:Key="boolVisibilityConverter" />
</UserControl.Resources>
<Grid>
<local:ImageButton Image="/SilverlightApplication11;component/Themes/hand.png" Command="{Binding ButtonCommand}" Visibility="{Binding FallbackValue=Visible, Path=ToolIsAvailable, Converter={StaticResource boolVisibilityConverter} }"/>
</Grid>
</UserControl>
and the code behind
public Test(TestCommandViewModel vm)
{
InitializeComponent();
this.Loaded += (o, e) => this.DataContext = vm;
}
My application is developed using wpf MVVM pattern where i have a list box which shows a set of operations to be selected with checkbox to check/uncheck. I need to get the selected item whenever a checkbox is checked / unchecked. I am binding the IsChecked property of checkbox to property in my model and selecteditem property of listbox to property in my viewmodel. Whenever i check/uncheck the frist item in the list the selected item event is triggering however the same is not getting triggered when i check/uncheck any item other than the first selected item in the list. I need to capture the changes whenever the user does any changes to listbox items.
Here is my view:
<ListBox Height="280" Width="Auto" ItemsSource="{Binding OperationsInfoCol}" SelectionMode="Multiple"
SelectedItem="{Binding Path=SelectedOperationItem,UpdateSourceTrigger=PropertyChanged}" IsEnabled="{Binding CanEnableListBox}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding OperationName}"
IsChecked="{Binding Path=IsOperationSelected,Mode=TwoWay}" IsEnabled="{Binding Path=CanEnableOperation,Mode=TwoWay}"/>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsOperationSelected,Mode=TwoWay}"/>
<Setter Property="IsEnabled" Value="{Binding CanEnableOperation,Mode=TwoWay}"/>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
ViewModel:
public OperationsInfo SelectedOperationItem
{
get
{
return m_oOperationSelected;
}
set
{
if (value != null)
{
m_oOperationSelected = value;
OnPropertyChanged("SelectedOperationItem");
if (null != m_oOperationSelected)
{
ObservableCollection<OperationsInfo> oCol = new ObservableCollection<OperationsInfo>();
//if (m_oOperationSelected.CanEnableOperation)
{
foreach (OperationsInfo itm in OperationsInfoCol)
{
if (itm.OperationId == m_oOperationSelected.OperationId && m_oOperationSelected.CanEnableOperation)
{
itm.IsOperationSelected = !m_oOperationSelected.IsOperationSelected;
}
oCol.Add(itm);
}
OperationsInfoCol.Clear();
OperationsInfoCol = oCol;
}
}
}
}
}
Model:
public class OperationsInfo {
private string m_strOperationName;
private int m_nOperationId;
private bool m_bIsOperationSelected;
private bool m_bCanEnable;
private LicenseManagerViewModel m_VMLicenseManager;
public bool IsOperationSelected
{
get
{
return m_bIsOperationSelected;
}
set
{
m_bIsOperationSelected = value;
LicenseManagerVM.OperationInfoChecked = value;
}
}
}
Because you set SelectionMode="Multiple", you cannot use SelectedItem.
You also cannot bind to SelectedItems because this property is read-only.
Not all is lost because in your code you bind IsSelected to IsOperationSelected
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected"
Value="{Binding IsOperationSelected,Mode=TwoWay}"/>
</Style>
</ListBox.ItemContainerStyle>
So now you can process the selected items using IsOperationSelected as indicated in the following example in your ViewModel:
foreach (var operationsInfo in OperationsInfoCol)
{
if ( operationsInfo.IsOperationSelected)
{
// do something...
}
}
You should probably bind IsChecked to the IsSelected property of the container ListBoxItem
That way you can handle the SelectionChanged event of the ListBox and react to any changes. (use e.AddedItems and e.RemovedItems to find out what changes where made.)
Some code example:
<ListBox ItemsSource="{Binding Data}" SelectionChanged="ListBox_SelectionChanged" SelectionMode="Extended">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Name}" IsChecked="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem}, Path=IsSelected}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Behind:
private void ListBox_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
ListBox lb = sender as ListBox;
if (e.AddedItems.Count > 0)
{
foreach (Employee emp in e.AddedItems.Cast<Employee>()) MessageBox.Show("Added: " + emp.Name);
}
if (e.RemovedItems.Count > 0)
{
foreach (Employee emp in e.RemovedItems.Cast<Employee>()) MessageBox.Show("Removed: " + emp.Name);
}
}