When PropertyChanged called event PropertyChanged=null - wpf

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.

Related

CheckBox binding not updating source

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.

WPF DataTrigger stops applying IsExpanded to Expander if user manually expands

I want an expander to expand if a flag in the VM is set. I also want the user to be able to override this and expand/collapse at will. The following code doesn't work, the timer kicks in and the expander expands and collapses repeatedly - then If you click the expander manually it swiches too - but the trigger fails to expand or collapse the expander. Its of course as if the manually keyed value is set and is taking priority over the Trigger Setter.
<Expander Header="Test" BorderThickness="2" BorderBrush="Black" VerticalAlignment="Bottom">
<Expander.Style >
<Style TargetType="Expander">
<Setter Property="IsExpanded" Value="True"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding DataContext.AmSet,
RelativeSource={RelativeSource AncestorType=Grid}}"
Value="True">
<Setter Property="IsExpanded" Value="False"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Expander.Style>
<Expander.Content>
<Border Background="AliceBlue" Width="50" Height="50"></Border>
</Expander.Content>
The VM has a dummy timer that just switches the flag to trigger the update as below
public class vm : INotifyPropertyChanged
{
public vm()
{
t = new System.Timers.Timer(1000);
t.Elapsed += t_Elapsed;
t.Start();
}
bool _AmSet = false;
public bool AmSet
{
get { return _AmSet; }
set
{
_AmSet = value;
OnPropertyChanged("");
}
}
void t_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
AmSet = !AmSet;
}
System.Timers.Timer t;
private void OnPropertyChanged(string prop)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Is there a reason you need to do this with a DataTrigger? It could be achieved easily with a two-way binding.
<Expander Header="Test" BorderThickness="2" BorderBrush="Black" VerticalAlignment="Bottom" IsExpanded="{Binding AmSet, Mode=TwoWay}"/>

Using DataGridComboBoxColumn as autocompletecombobox in a DataGrid

I want to use the DataGridComboBoxColumn as a autocomplete combobox.
I've got it partially working. When the Row is in EditMode I can type text in the ComboBox, also in ViewMode the control returns the text. Only how to get the Label (in template) to EditMode by mouse doubleclick?
Up front, I don't want to use the DataGridTemplateColumn control because it just doesn't handle keyboard and mouse entry like the DataGridComboBoxColumn does (tabs, arrows, edit/view mode/ double click etc..).
It looks like:
I fixed it adding a behavior to the TextBox to get a link to the parent DataGrid then setting the Row into Edit Mode by calling BeginEdit().
The solution I used:
View
<Window x:Class="WpfApplication1.MainWindow"
xmlns:local="clr-namespace:WpfApplication1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Window.Resources>
<local:BindingProxy x:Key="proxy" Data="{Binding}" />
</Window.Resources>
<Grid>
<DataGrid ItemsSource="{Binding Model.Things}" Name="MyGrid" ClipboardCopyMode="IncludeHeader">
<DataGrid.Resources>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridComboBoxColumn Header="Object" MinWidth="140" TextBinding="{Binding ObjectText}" ItemsSource="{Binding Source={StaticResource proxy}, Path=Data.Model.ObjectList}" >
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter Property="IsEditable" Value="True"/>
<Setter Property="Text" Value="{Binding ObjectText}"/>
<Setter Property="IsSynchronizedWithCurrentItem" Value="True" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<TextBox IsReadOnly="True" Text="{Binding Path=DataContext.ObjectText, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridRow}}}">
<TextBox.Resources>
<Style TargetType="{x:Type TextBox}">
<Setter Property="local:CellSelectedBehavior.IsCellRowSelected" Value="true"></Setter>
</Style>
</TextBox.Resources>
</TextBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGridComboBoxColumn.ElementStyle>
</DataGridComboBoxColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
Model
public class Model : BaseModel
{
//List of objects for combobox
private List<string> _objectList;
public List<string> ObjectList { get { return _objectList; } set { _objectList = value; } }
//Rows in datagrid
private List<Thing> _things;
public List<Thing> Things
{
get { return _things; }
set { _things = value; OnPropertyChanged("Things"); }
}
}
public class Thing : BaseModel
{
//Text in combobox
private string _objectText;
public string ObjectText
{
get { return _objectText; }
set { _objectText = value; OnPropertyChanged("ObjectText"); }
}
}
ViewModel
public class ViewModel
{
public Model Model { get; set; }
public ViewModel()
{
Model = new WpfApplication1.Model();
Model.ObjectList = new List<string>();
Model.ObjectList.Add("Aaaaa");
Model.ObjectList.Add("Bbbbb");
Model.ObjectList.Add("Ccccc");
Model.Things = new List<Thing>();
Model.Things.Add(new Thing() { ObjectText = "Aaaaa" });
}
}
Behavior
public class CellSelectedBehavior
{
public static bool GetIsCellRowSelected(DependencyObject obj) { return (bool)obj.GetValue(IsCellRowSelectedProperty); }
public static void SetIsCellRowSelected(DependencyObject obj, bool value) { obj.SetValue(IsCellRowSelectedProperty, value); }
public static readonly DependencyProperty IsCellRowSelectedProperty = DependencyProperty.RegisterAttached("IsCellRowSelected",
typeof(bool), typeof(CellSelectedBehavior), new UIPropertyMetadata(false, OnIsCellRowSelected));
static void OnIsCellRowSelected(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
TextBox item = depObj as TextBox;
if (item == null)
return;
if (e.NewValue is bool == false)
return;
if ((bool)e.NewValue)
item.MouseDoubleClick += SelectRow;
else
item.MouseDoubleClick -= SelectRow;
}
static void SelectRow(object sender, EventArgs e)
{
TextBox box = sender as TextBox;
var grid = box.FindAncestor<DataGrid>();
grid.BeginEdit();
}
}
Helper (to find DataGrid)
public static class Helper
{
public static T FindAncestor<T>(this DependencyObject current) where T : DependencyObject
{
current = VisualTreeHelper.GetParent(current);
while (current != null)
{
if (current is T)
{
return (T)current;
}
current = VisualTreeHelper.GetParent(current);
};
return null;
}
}

Databinding to Command in Silverlight Templated Button control

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

Listbox with checkbox not triggering selected item when checkbox is checked/unchecked

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

Resources