EventTrigger in WPF - wpf

I want to get the SelectedItems from the Listbox where the Checkbox is selected via the InvokeCommandAction and store them in an obsevableCollection SelectedItems , but I am not getting the SelectedItemChangedCommand working(break point does not hit) and not sure how do I populate the Items in the SelectedItems Collection. I tried following, hoping that once the checkbox is checked or unchecked, the SelectedItemChangedCommand would be called and I can have a method getting called on this where I will populate the SelectedItems
Please note I am looking for a way to achieve this without any code behind.
<ListBox Margin="45,7,0,0" VerticalAlignment="Top" Grid.Column="1" Grid.Row="0" Grid.RowSpan="2"
ItemsSource="{Binding ListItems}"
SelectionMode="Multiple" Height="146">
<ListBox.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="OverridesDefaultStyle" Value="true" />
<Setter Property="SnapsToDevicePixels" Value="true" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<CheckBox Margin="5,2"
IsChecked="{TemplateBinding IsSelected}">
<ContentPresenter />
</CheckBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.Resources>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding SelectedItemChangedCommand}" CommandParameter="{Binding ElementName=myListBox, Path=SelectedItem}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
Updated Xaml File
<Window x:Class="stack.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:stack"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<ListBox x:Name="myListBox" Margin="45,7,0,0" VerticalAlignment="Top"
ItemsSource="{Binding ListItems}"
SelectionMode="Multiple" Height="146">
<ListBox.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="OverridesDefaultStyle" Value="true" />
<Setter Property="SnapsToDevicePixels" Value="true" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<CheckBox
Margin="5,2"
IsChecked="{Binding IsSelected, RelativeSource={RelativeSource TemplatedParent}}"
>
<ContentPresenter />
</CheckBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.Resources>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding SelectedItemChangedCommand}"
CommandParameter="{Binding ElementName=myListBox, Path=SelectedItem}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
</Grid>
</Window>
I am binding the listbox to observableCollection ListItems defined in view model as
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace stack
{
public class MainViewModel : INotifyPropertyChanged
{
public ObservableCollection<string> ListItems { get; set; }
public ObservableCollection<string> SelectedListItems { get; set; }
public RelayCommand SelectedItemChangedCommand { get; set; }
public string _selectedItem;
public string SelectedItem
{
get => _selectedItem;
set
{
_selectedItem = value;
OnPropertyChanged("SelectedItem");
}
}
public bool _isSelected;
public bool IsSelected
{
get => _isSelected;
set
{
_isSelected = value;
OnPropertyChanged("IsSelected");
}
}
public MainViewModel()
{
ListItems = new ObservableCollection<string>();
ListItems.Add("One");
ListItems.Add("Two");
ListItems.Add("three");
ListItems.Add("Four");
ListItems.Add("Five");
SelectedItemChangedCommand = new RelayCommand(this.ExecuteItemChanged);
}
public void ExecuteItemChanged(object parameter)
{
if (IsSelected)
{
SelectedListItems.Add(SelectedItem);
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler _propertyChangedEventHandler = PropertyChanged;
_propertyChangedEventHandler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

TemplateBinding is cheap, but it doesn't do two-way bindings. Thus, the items aren't getting selected. You need a regular binding with a RelativeSource of TemplatedParent:
<CheckBox
Margin="5,2"
IsChecked="{Binding IsSelected, RelativeSource={RelativeSource TemplatedParent}}"
>
Note that you are binding CheckBox.IsChecked to ListBoxItem.IsSelected. The templated parent is a ListBoxItem, not your main viewmodel.
Second, you want to pass the SelectedItems, plural, property to your command. SelectedItem is singular. It's only one item. It'll be the topmost selected item when many are selected. You have SelectionMode="Multiple" on your listbox, so I assume you want the full selection.
<i:InvokeCommandAction
Command="{Binding SelectedItemChangedCommand}"
CommandParameter="{Binding ElementName=myListBox, Path=SelectedItems}"
/>
And make sure you have x:Name="myListBox" on the ListBox: The CommandParameter binding needs that to find SelectedItems.
Finally: ExecuteItemChanged() is very broken. Your main viewmodel properties IsSelected and SelectedItem are not bound to anything. They're just false and null, always. Every time the selection changes, you execute the command and pass in the first selected item as parameter, and then you ignore it and go look to see if false is still false, which it is. If it weren't, your viewmodel's SelectedItem property would still be null, because you never updated that either.
Here's what you want to do: When the selection changes, pass the entire collection of currently selected items into your command. Replace the viewmodel's entire collection of currently selected items with the current state from the control. You must, must, must bind SelectedItems as the CommandParameter above.
Get rid of SelectedItem and IsSelected on the viewmodel, they serve no purpose.
If possible, don't ever get into this business of maintaining two lists and trying to keep them in sync piecemeal. It's always a mess. And you don't need to do it in this case.
public void ExecuteItemChanged(object parameter)
{
// ListBox.SelectedItems is System.Windows.Controls.SelectedItemCollection,
// a precambrian monster that's declared internal in PresentationFramework.dll.
// However, it does implement non-generic IList, so cast it to that.
if (parameter is System.Collections.IList selectedItems)
{
if (SelectedListItems == null)
{
SelectedListItems = new ObservableCollection<String>();
}
SelectedListItems.Clear();
foreach (string item in selectedItems)
{
SelectedListItems.Add(item);
}
}
}

Related

How to binding different data context to data template?

I try to simplify my Main user control that contains 8 user controls that are exactly the same but they are binding to different VM to display its data.
Currently, I have to create a template for each of my user control and binding to each of VM.
It seems that I can create one data template for all 8 user controls and apply the data template to each of the user control with different instance of VM.
Here are my code that current I have to use different templates for different dependency of View Model containing the data of each gauge
<DataTemplate x:Key="AnalogIO1Template" DataType="{x:Type local:CAnalogIOVM}">
<local:ucAnalogIO
GaugeValueDP="{Binding Path=GaugeValue1VMDP.GaugeValue, ElementName=ucAnalogIOWindow}">
</local:ucAnalogIO>
</DataTemplate>
<DataTemplate x:Key="AnalogIO1Template" DataType="{x:Type local:CAnalogIOVM}">
<local:ucAnalogIO
GaugeValueDP="{Binding Path=GaugeValue2VMDP.GaugeValue, ElementName=ucAnalogIOWindow}">
</local:ucAnalogIO>
</DataTemplate>
<Grid Background="#FFE3E2D7" Grid.Column="0" Grid.ColumnSpan="1" Grid.Row="0" Grid.RowSpan="1" Margin="0,0,0,1">
<ContentControl x:Name="ucLinearGauge1">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource AnalogIO1Template }" />
</Style>
</ContentControl.Style>
</ContentControl>
</Grid>
<Grid Background="#FFE3E2D7" Grid.Column="1" Grid.ColumnSpan="1" Grid.Row="0" Grid.RowSpan="1" Margin="0,0,0,1">
<ContentControl x:Name="ucLinearGauge2">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource AnalogIO2Template }" />
</Style>
</ContentControl.Style>
</ContentControl>
</Grid>
Here I try to create one Data template for all 8 user controls but it does not work
<DataTemplate x:Key="AnalogIOTemplate" DataType="{x:Type local:CAnalogIOVM}">
<local:ucAnalogIO
GaugeValueDP="{Binding Path=GaugeValue, ElementName=ucAnalogIOWindow}">
</local:ucAnalogIO>
</DataTemplate>
<Grid Background="#FFE3E2D7" Grid.Column="0" Grid.ColumnSpan="1" Grid.Row="0" Grid.RowSpan="1" Margin="0,0,0,1">
<ContentControl Content="{Binding Path=GaugeValue1VMDP}" x:Name="ucLinearGauge1">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource AnalogIOTemplate}" />
</Style>
</ContentControl.Style>
</ContentControl>
</Grid>
Is there a way to binding different data context to the data template?
Thanks
If class of UserControl, class of VM and bindings between them are identical and the only difference is instances of VM, creating a Style for UserControl and binding each instance of VM with DataContext of corresponding instance of UserControl would be enough.
Since we don't know actual code of your UserControl and VM, I will show this by samples.
Sample UserControl which has Id dependency property and can show its value:
<UserControl x:Class="WpfApp.SampleUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<TextBlock x:Name="IdTextBlock"/>
</StackPanel>
</UserControl>
public partial class SampleUserControl : UserControl
{
public int Id
{
get { return (int)GetValue(IdProperty); }
set { SetValue(IdProperty, value); }
}
public static readonly DependencyProperty IdProperty =
DependencyProperty.Register("Id", typeof(int), typeof(SampleUserControl),
new PropertyMetadata(0, (d, e) => ((SampleUserControl)d).IdTextBlock.Text = e.NewValue.ToString()));
public SampleUserControl()
{
InitializeComponent();
}
}
Sample VM which has Id property and MainWindow's VM which has instances of sample VM:
// using Microsoft.Toolkit.Mvvm.ComponentModel;
public class SampleViewModel : ObservableObject
{
private int _id;
public int Id
{
get => _id;
set => SetProperty(ref _id, value);
}
}
public class MainWindowViewModel : ObservableObject
{
public SampleViewModel? VM1 { get; }
public SampleViewModel? VM2 { get; }
public MainWindowViewModel()
{
VM1 = new SampleViewModel { Id = 10 };
VM2 = new SampleViewModel { Id = 20 };
}
}
Finally, bind each instance of sample VM with DataContext of corresponding instance of sample UserControl so that Id of sample VM is bound with Id of sample UserControl.
<Window x:Class="WpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp"
Title="MainWindow"
Width="400" Height="200">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Window.Resources>
<Style TargetType="{x:Type local:SampleUserControl}">
<Setter Property="Id" Value="{Binding Id}"/>
</Style>
</Window.Resources>
<StackPanel>
<local:SampleUserControl DataContext="{Binding VM1}"/>
<local:SampleUserControl DataContext="{Binding VM2}"/>
</StackPanel>
</Window>

wpf how to get value from textbook in ControlTemplate treeivwitem

how to get txt_add.text value?
this style applied to TreeViewitem in code behind
<Page.Resources>
<Style TargetType="{x:Type TreeViewItem}" x:Key="add" >
<Setter Property="Background" Value="{DynamicResource WhiteBrush}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TreeViewItem" >
<StackPanel Orientation="Horizontal" Margin="5">
<TextBox Width="300" Controls:TextBoxHelper.Watermark="Account Name" Margin="2" x:Name="txt_add"/>
<Button Content="{x:Static lang:ResLang.insert}" Style="{StaticResource ButtonSystem}" Width="100" Margin="2" Click="Button_AddNewSubOk_Click"/>
<Button Content="{x:Static lang:ResLang.btn_cancel}" Style="{StaticResource ButtonCancel }" Width="100" Margin="2" Click="Button_AddNewSubCancel_Click"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Page.Resources>
</Page>
You need to bind it using mvvm pattern, create a viewmodel class which inherits inotifypropertychanged then bind your text to property in that class.
<Window.DataContext>
<model:viewmodel x:Key="viewmodel"/>
</Window.DataContext>
<!-- where ever you got your textbox -->
<TextBox Text="{Binding Mode=TwoWay,Source={StaticResource viewmodel},Path=stringproperty"/>
and simple viewmodel class :
public class viewmodel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public String stringproperty{ get; set; } ;
}
there is simple implementation of "property changed" in web if this is not working for you.
and you can access it like this but this is wrong you should'nt do this
(this.DataContext as viewmodel).stringproperty
after binding only use bindings to access your data , if you need them in some actions or events pass as parameter to "command" you could search about that

Attach XAML Behavior to all controls of same type

I have an InvokeCommandAction that I have that is attached to the GotFocus event of a TextBox like so:
<TextBox Grid.Row="0"
Grid.Column="1"
Width="40"
HorizontalAlignment="Right">
<i:Interaction.Triggers>
<i:EventTrigger EventName="GotFocus">
<i:InvokeCommandAction Command="{Binding GotFocusCommand}" CommandParameter="Enter data [message to be displayed]" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
It works just fine this way, but I have dozens of TextBoxes with this same setup. Rather than repeating the code (as I am currently doing for every one), I am hoping to just attach that trigger to all controls of type {x:Type TextBox}.
Normally, I would set properties in the Resources section, like this:
<UserControl.Resources>
<Style TargetType="TextBlock">
<Setter Property="Padding" Value="5,0,0,0" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
</UserControl.Resources>
Unfortunately, this will not work for Triggers:
The attached property "Triggers" can only be applied to types that are derived from "DependencyObject".
Ideally, I would like to do something like this:
<UserControl.Resources>
<Style TargetType="{x:Type TextBox}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="GotFocus">
<i:InvokeCommandAction Command="{Binding GotFocusCommand}" CommandParameter="{Binding Tag}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Style>
</UserControl.Resources>
Where I then just need to set the Tag property of each TextBox to specify the message to be displayed. Am I on the right track? Do I need to change it to use a ControlTemplate or something like that?
EDIT
I have seen a similar question here: Interaction Triggers in Style in ResourceDictionary WPF
After reading the answers for that question, I tried the following:
<UserControl.Resources>
<TextBox x:Key="TextBoxWithTag">
<i:Interaction.Triggers>
<i:EventTrigger EventName="GotFocus">
<i:InvokeCommandAction Command="{Binding GotFocusCommand}" CommandParameter="{Binding Path=Tag, RelativeSource={RelativeSource Self}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
</UserControl.Resources>
Then assigning to a control like so:
<ContentControl Grid.Row="0"
Grid.Column="1"
Width="40"
HorizontalAlignment="Right"
Content="{StaticResource TextBoxWithTag}"
Tag="test tag" />
This also does not work, still complaining about:
The attached property "Triggers" can only be applied to types that are derived from "DependencyObject".
EDIT 2
Here is the GotFocusCommand information. It sets the value of a string that has a TextBlock bound to it.
This is in my ViewModel:
private ICommand _gotFocusCommand;
public ICommand GotFocusCommand
{
get
{
if (_gotFocusCommand == null)
{
_gotFocusCommand = new DelegateCommand<string>(TextBoxGotFocus);
}
return _gotFocusCommand;
}
}
private void TextBoxGotFocus(string infoText)
{
CardInfoText = infoText;
}
Then the XAML:
<TextBlock Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="2"
HorizontalAlignment="Center"
Text="{Binding CardInfoText}" />
There are several ways to do what you want. One example:
public static class UIBehaviors {
public static readonly DependencyProperty AttachedTriggersProperty = DependencyProperty.RegisterAttached(
"AttachedTriggers", typeof (EventTriggerCollection), typeof (UIBehaviors), new PropertyMetadata(null, OnAttachedTriggersChanged));
private static void OnAttachedTriggersChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var triggers = Interaction.GetTriggers(d);
if (e.OldValue != null) {
foreach (var trigger in (EventTriggerCollection) e.OldValue) {
triggers.Remove(trigger);
}
}
if (e.NewValue != null) {
foreach (var trigger in (EventTriggerCollection) e.NewValue) {
triggers.Add(trigger);
}
}
}
public static void SetAttachedTriggers(DependencyObject element, EventTriggerCollection value) {
element.SetValue(AttachedTriggersProperty, value);
}
public static EventTriggerCollection GetAttachedTriggers(DependencyObject element) {
return (EventTriggerCollection) element.GetValue(AttachedTriggersProperty);
}
}
public class EventTriggerCollection : Collection<EventTrigger> {
}
Here we declare attached property which accepts set of EventTrigger (from Interactivity assembly). When this property is set, we just attach all those triggers, like i:Interaction.Triggers will do. Then use it like this:
<Window.Resources>
<local:EventTriggerCollection x:Shared="False" x:Key="textBoxTriggers">
<i:EventTrigger EventName="GotFocus">
<i:InvokeCommandAction Command="{Binding GotFocusCommand}" CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=TextBox}, Path=Tag}" />
</i:EventTrigger>
</local:EventTriggerCollection>
<Style TargetType="{x:Type TextBox}">
<Setter Property="local:UIBehaviors.AttachedTriggers" Value="{StaticResource textBoxTriggers}"/>
</Style>
</Window.Resources>
Note how I bind to TextBox.Tag property. You cannot just bind to it as in your question, because your data context will be view model (with GotFocusCommand).
Also note how triggers collection is moved as a separate element in resource dictionary, and x:Shared="false" set for it. This will cause creating new set of triggers each time this property is accessed, so each TextBox will have it's own set of triggers.
Then any
<TextBox Text="Test" Tag="test message" />
Will call GotFocusCommand on data context of TextBox, with "test message" as parameter.

RadioButton IsChecked property gets overridden when changing tabs

I'm sure this behavior is known, but I'm unable to google it. I have following code:
<Window x:Class="ContentControlListDataTemplateKacke.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<DockPanel>
<TabControl ItemsSource="{Binding Items}">
<TabControl.ContentTemplate>
<DataTemplate>
<StackPanel>
<Label Content="{Binding Name}" />
<RadioButton Content="Option1" IsChecked="{Binding Option1}" />
<RadioButton Content="Option2" IsChecked="{Binding Option2}" />
</StackPanel>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</DockPanel>
</Window>
The code-behind is simple:
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
The ViewModel looks like this:
public class ViewModel : NotificationObject
{
public ViewModel()
{
Items = new ObservableCollection<Item>
{
new Item {Name = "1", Option1 = true},
new Item {Name = "2", Option2 = true}
};
}
public ObservableCollection<Item> Items { get; set; }
}
And an Item like this:
public class Item : NotificationObject
{
public string Name { get; set; }
private bool _option1;
public bool Option1
{
get { return _option1; }
set
{
_option1 = value;
RaisePropertyChanged(() => Option1);
}
}
private bool _option2;
public bool Option2
{
get { return _option2; }
set
{
_option2 = value;
RaisePropertyChanged(() => Option2);
}
}
}
I'm using Prism, so the RaisePropertyChanged raises an PropertyChanged-event. Select the second tab, then the first tab, then the second tab again and voilá, the RadioButtons on the second tab are deselected.
Why?
Another solution apart from Rachels
A colleague of mine just had the idea to bind the GroupName property of the RadioButtons to a unique string of each item. Just change the declaration of the RadioButtons into this:
<RadioButton GroupName="{Binding Name}" Content="Option1" IsChecked="{Binding Option1}" />
<RadioButton GroupName="{Binding Name}" Content="Option2" IsChecked="{Binding Option2}" />
And it works if the Name-property is unique for all items (as its the case for my problem).
WPF is reading all the RadioButtons as part of the same Group, and in a radio button group only one item can be selected at a time.
The load order goes:
Load Tab1
Load Tab1.Radio1. IsChecked = True
Load Tab1.Radio2. IsChecked = True, so set Tab1.Radio2.IsChecked = False
Click Tab 2
Load Tab2
Load Tab2.Radio1. IsChecked = True, so set Tab1.Radio2.IsChecked = False
Load Tab2.Radio2. IsChecked = True, so set Tab2.Radio1.IsChecked = False
By now, Tab2.Radio2 is the only one checked, and all the other Radios have been loaded and Unchecked, so their DataBound values have been updated to false.
Click Tab 1
Load Tab1.Radio1. IsChecked = False
Load Tab1.Radio2. IsChecked = False
If you Radio buttons are unrelated and can both be checked at once, I would suggest switching to CheckBoxes
If they're meant to be grouped and only one item can be selected at a time, I'd suggest switching to a ListBox drawn with RadioButtons, and only storing the SelectedOption in your ViewModel
Here's the style I typically use for that:
<Style x:Key="RadioButtonListBoxStyle" TargetType="{x:Type ListBox}">
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="KeyboardNavigation.DirectionalNavigation" Value="Cycle" />
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListBoxItem}" >
<Setter Property="Margin" Value="2, 2, 2, 0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border Background="Transparent">
<RadioButton
Content="{TemplateBinding ContentPresenter.Content}" VerticalAlignment="Center"
IsChecked="{Binding Path=IsSelected,RelativeSource={RelativeSource TemplatedParent},Mode=TwoWay}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
It's used like this:
<ListBox ItemsSource="{Binding Options}"
SelectedValue="{Binding SelectedValue}"
Style="{StaticResource RadioButtonListBoxStyle}" />
I used to have a problem very similar to this one where text was getting cleared when I was switching between views. If I recall correctly the following solution was what worked.
Bind OneWay to the properties and mark these bound properties with an attribute.
Every time you load the view (and hence viewmodel), use reflection on the aforementioned attribute to find the bound properties.
Fire off a PropertyChanged event for each of the properties to update the view correctly.
I think this results from the view loading with default settings and not querying the properties on load since nothing is raising a PropertyChanged event.
Also, it's not part of your question, but you can set the data context in XAML (via the DataContext property in Window) directly so that Visual Studio doesn't have to have an explicit codebehind file.

Binding ContentControl to an ObservableCollection if Count == 1

how can I bind the Content of a ContentControl to an ObservableCollection.
The control should show an object as content only if the ObservableColelction contains exactly one object (the object to be shown).
Thanks,
Walter
This is easy. Just use this DataTemplate:
<DataTemplate x:Key="ShowItemIfExactlyOneItem">
<ItemsControl x:Name="ic">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate><Grid/></ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Count}" Value="1">
<Setter TargetName="ic" Property="ItemsSource" Value="{Binding}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
This is used as the ContentTemplate of your ContentControl. For example:
<Button Content="{Binding observableCollection}"
ContentTemplate="{StaticResource ShowItemIfExactlyOneItem}" />
That's all you need to do.
How it works: The template normally contains an ItemsControl with no items, which is invisible and has no size. But if the ObservableCollection that is set as Content ever has exactly one item in it (Count==1), the trigger fires and sets the ItemsSource of the ItmesControl, causing the single item to display using a Grid for a panel. The Grid template is required because the default panel (StackPanel) does not allow its content to expand to fill the available space.
Note: If you also want to specify a DataTemplate for the item itself rather than using the default template, set the "ItemTemplate" property of the ItemsControl.
+1, Good question :)
You can bind the ContentControl to an ObservableCollection<T> and WPF is smart enough to know that you are only interested in rendering one item from the collection (the 'current' item)
(Aside: this is the basis of master-detail collections in WPF, bind an ItemsControl and a ContentControl to the same collection, and set the IsSynchronizedWithCurrentItem=True on the ItemsControl)
Your question, though, asks how to render the content only if the collection contains a single item... for this, we need to utilize the fact that ObservableCollection<T> contains a public Count property, and some judicious use of DataTriggers...
Try this...
First, here's my trivial Model object, 'Customer'
public class Customer
{
public string Name { get; set; }
}
Now, a ViewModel that exposes a collection of these objects...
public class ViewModel
{
public ViewModel()
{
MyCollection = new ObservableCollection<Customer>();
// Add and remove items to check that the DataTrigger fires correctly...
MyCollection.Add(new Customer { Name = "John Smith" });
//MyCollection.Add(new Customer { Name = "Mary Smith" });
}
public ObservableCollection<Customer> MyCollection { get; private set; }
}
Set the DataContext in the Window to be an instance of the VM...
public Window1()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
and here's the fun bit: the XAML to template a Customer object, and set a DataTrigger to remove the 'Invalid Count' part if (and only if) the Count is equal to 1.
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate x:Name="template">
<Grid>
<Grid Background="AliceBlue">
<TextBlock Text="{Binding Name}" />
</Grid>
<Grid x:Name="invalidCountGrid" Background="LightGray" Visibility="Visible">
<TextBlock
VerticalAlignment="Center" HorizontalAlignment="Center"
Text="Invalid Count" />
</Grid>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Count}" Value="1">
<Setter TargetName="invalidCountGrid" Property="Visibility" Value="Collapsed" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<ContentControl
Margin="30"
Content="{Binding MyCollection}" />
</Window>
UPDATE
To get this dynamic behaviour working, there is another class that will help us... the CollectionViewSource
Update your VM to expose an ICollectionView, like:
public class ViewModel
{
public ViewModel()
{
MyCollection = new ObservableCollection<Customer>();
CollectionView = CollectionViewSource.GetDefaultView(MyCollection);
}
public ObservableCollection<Customer> MyCollection { get; private set; }
public ICollectionView CollectionView { get; private set; }
internal void Add(Customer customer)
{
MyCollection.Add(customer);
CollectionView.MoveCurrentTo(customer);
}
}
And in the Window wire a button Click event up to the new 'Add' method (You can use Commanding if you prefer, this is just as effective for now)
private void Button_Click(object sender, RoutedEventArgs e)
{
_viewModel.Add(new Customer { Name = "John Smith" });
}
Then in the XAML, without changing the Resource at all - make this the body of your Window:
<StackPanel>
<TextBlock Height="20">
<TextBlock.Text>
<MultiBinding StringFormat="{}Count: {0}">
<Binding Path="MyCollection.Count" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<Button Click="Button_Click" Width="80">Add</Button>
<ContentControl
Margin="30" Height="120"
Content="{Binding CollectionView}" />
</StackPanel>
So now, the Content of your ContentControl is the ICollectionView, and you can tell WPF what the current item is, using the MoveCurrentTo() method.
Note that, even though ICollectionView does not itself contain properties called 'Count' or 'Name', the platform is smart enough to use the underlying data source from the CollectionView in our Bindings...

Resources