BarButtonItems and BarSubItems on Bound RibbonControl - wpf

I am developing a WPF application using DevExpress controls, such as the Ribbon control. I want to be able to place buttons on the ribbon dynamically. I would like to be able to support both regular buttons and drop-down buttons.
I was thinking something similar to below.
WPF View:
<UserControl.Resources>
<DataTemplate x:Key="RibbonCommandTemplate">
<ContentControl>
<dxb:BarButtonItem RibbonStyle="All" Content="{Binding Caption}"
Command="{Binding (dxr:RibbonControl.Ribbon).DataContext.MenuExecuteCommand,
RelativeSource={RelativeSource Self}}"
CommandParameter="{Binding}" />
</ContentControl>
</DataTemplate>
</UserControl.Resources>
<Grid>
<DockPanel>
<dxr:RibbonControl DockPanel.Dock="Top" RibbonStyle="Office2010">
<dxr:RibbonDefaultPageCategory>
<dxr:RibbonPage Caption="Home">
<dxr:RibbonPageGroup Caption="Dynamic Commands"
ItemLinksSource="{Binding DynamicCommands}"
ItemTemplate="{StaticResource RibbonCommandTemplate}" />
</dxr:RibbonPage>
</dxr:RibbonDefaultPageCategory>
</dxr:RibbonControl>
<Grid/>
</DockPanel>
</Grid>
View Model:
public class RibbonCommand
{
public string Caption { get; set; }
public int CommandCode { get; set; }
public ObservableCollection<RibbonCommand> SubItems { get; set; }
public bool HasSubItems
{
get
{
if (SubItems != null)
return (SubItems.Count > 0);
else
return false;
}
}
}
[POCOViewModel]
public class MainViewModel
{
public ObservableCollection<RibbonCommand> DynamicCommands { get; set; }
public MainViewModel()
{
DynamicCommands = new ObservableCollection<RibbonCommand>();
// Regular buttons.
DynamicCommands.Add(new RibbonCommand() { Caption = "Button 1", CommandCode = 1 });
DynamicCommands.Add(new RibbonCommand() { Caption = "Button 2", CommandCode = 2 });
// Drop-down button.
RibbonCommand dropDownCommand = new RibbonCommand() { Caption = "Drop-Down", CommandCode = 3 };
dropDownCommand.SubItems = new ObservableCollection<RibbonCommand>();
dropDownCommand.SubItems.Add(new RibbonCommand() { Caption = "Sub-Item 1", CommandCode = 31 });
dropDownCommand.SubItems.Add(new RibbonCommand() { Caption = "Sub-Item 2", CommandCode = 32 });
dropDownCommand.SubItems.Add(new RibbonCommand() { Caption = "Sub-Item 3", CommandCode = 33 });
DynamicCommands.Add(dropDownCommand);
}
public void MenuExecute(RibbonCommand command)
{
MessageBox.Show(string.Format("You clicked command with ID: {0} (\"{1}\").",
command.CommandCode, command.Caption), "Bound Ribbon Control");
}
}
This code does successfully populate the ribbon with items I added in my DynamicCommands collection, but I would like to support drop-down buttons for items with anything in the SubItems collection (the third button on my example above).
Is there a way to conditionally change the type of control displayed in a DataTemplate. If the object's HasSubItems is true, I would like a BarSubItem placed on the ribbon. If it is false, I will keep the BarButtonItem.

If this is regular WPF rather than UWP, and if the DataContexts of your subitems are of different types, you can define multiple DataTemplates with DataType attributes in the RibbonPageGroup's resources (where they won't be in scope for anything that doesn't need them), and get rid of that ItemTemplate attribute:
<dxr:RibbonPageGroup
Caption="Dynamic Commands"
ItemLinksSource="{Binding DynamicCommands}">
<dxr:RibbonPageGroup.Resources>
<DataTemplate DataType="{x:Type local:RibbonCommand}">
<!-- XAML stuff -->
</DataTemplate>
<DataTemplate DataType="{x:Type local:SpecialRibbonCommand}">
<!-- Totally different XAML stuff -->
</DataTemplate>
</dxr:RibbonPageGroup.Resources>
<!-- etc -->
For another option, you should be able to write a DataTemplateSelector and give it to the RibbonControl's ToolbarItemTemplateSelector property or the RibbonPageGroup's ItemTemplateSelector property.
Lastly, write one complicated DataTemplate with multiple child controls superimposed in a Grid, and a series of triggers that show only the appropriate one based on properties of the DataContext. If you've only got two different options to handle, this may be the quickest and easiest route.
<DataTemplate x:Key="RibbonCommandTemplate">
<Grid>
<Label x:Name="OneThing" />
<Label x:Name="AnotherThing" />
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding HasSubItems}" Value="True">
<Setter TargetName="OneThing" Property="Visibility" Value="Collapsed" />
<Setter TargetName="AnotherThing" Property="Visibility" Value="Visible" />
</DataTrigger>
<!-- Other triggers for HasSubItems == False, whatever -->
</DataTemplate.Triggers>
</DataTemplate>
This seems pretty crude, but I've done it so much in WPF that I'm getting desensitized to it.

I figured out a way to do this using a DataTemplateSelector class:
using System.Windows;
using System.Windows.Controls;
using RibbonDynamicButtons.ViewModels;
namespace RibbonDynamicButtons.Selectors
{
public class RibbonCommandSelector : DataTemplateSelector
{
public DataTemplate CommandTemplate { get; set; }
public DataTemplate SubCommandTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if(item is RibbonCommand)
{
RibbonCommand command = (RibbonCommand)item;
if (command.HasSubItems)
return SubCommandTemplate;
else
return CommandTemplate;
}
return base.SelectTemplate(item, container);
}
}
}
I added my selector to the xaml as follows:
<UserControl
...
xmlns:Selectors="clr-namespace:RibbonDynamicButtons.Selectors">
<UserControlResources>
<DataTemplate x:Key="RibbonSubItemTemplate">
<ContentControl>
<dxb:BarButtonItem RibbonStyle="SmallWithText" Content="{Binding Caption}"
Command="{Binding (dxr:RibbonControl.Ribbon).DataContext.MenuExecuteCommand,
RelativeSource={RelativeSource Self}}" CommandParameter="{Binding}" />
</ContentControl>
</DataTemplate>
<Selectors:RibbonCommandSelector x:Key="RibbonCommandSelector">
<Selectors:RibbonCommandSelector.CommandTemplate>
<DataTemplate>
<ContentControl>
<dxb:BarButtonItem RibbonStyle="All"
Content="{Binding Caption}"
Command="{Binding (dxr:RibbonControl.Ribbon).DataContext.MenuExecuteCommand,
RelativeSource={RelativeSource Self}}"
CommandParameter="{Binding}" />
</ContentControl>
</DataTemplate>
</Selectors:RibbonCommandSelector.CommandTemplate>
<Selectors:RibbonCommandSelector.SubCommandTemplate>
<DataTemplate>
<ContentControl>
<dxb:BarSubItem RibbonStyle="All" Content="{Binding Caption}"
ItemLinksSource="{Binding SubItems}"
ItemTemplate="{StaticResource RibbonSubItemTemplate}" />
</ContentControl>
</DataTemplate>
</Selectors:RibbonCommandSelector.SubCommandTemplate>
</Selectors:RibbonCommandSelector>
</UserControlResources>
I bind the ItemTemplateSelector to my selector on the RibbonPageGroup:
<dxr:RibbonPageGroup Caption="Dynamic Commands" ItemLinksSource="{Binding DynamicCommands}"
ItemTemplateSelector="{StaticResource RibbonCommandSelector}" />
I did not need to make any changes to the View Model I included on my original question.

Related

Load controls on runtime based on selection

I'm new to XAML and I have a case where I need to change controls based on a selection on a combobox with templates.
For example, let's say that a user selects a template that requires a day of week and a time range that something will be available. I would like that, on the moment of the selection, the control with the information needed get build on the screen and that the bindings get to work as well.
Can someone give me a hint or indicate an article with an elegant way to do so?
Thanks in advance.
The solution you are looking for is a ContentControl and DataTemplates. You use the selected item of the ComboBox to change ContentTemplate of the Content Control.
You question mentions binding so I will assume you understand the MVVM pattern.
As an example, lets use MyModel1 as the Model
public class MyModel1
{
private Collection<string> values;
public Collection<string> Values { get { return values ?? (values = new Collection<string> { "One", "Two" }); } }
public string Field1 { get; set; }
public string Field2 { get; set; }
}
And MyViewModel as the ViewModel
public class MyViewModel
{
public MyViewModel()
{
Model = new MyModel1();
}
public MyModel1 Model { get; set; }
}
And the code behind does nothing but instantiate the ViewModel.
public partial class MainWindow : Window
{
public MainWindow()
{
ViewModel = new MyViewModel();
InitializeComponent();
}
public MyViewModel ViewModel { get; set; }
}
All three are very simple classes. The fun comes in the Xaml which is
<Window x:Class="StackOverflow._20893945.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:this="clr-namespace:StackOverflow._20893945"
DataContext="{Binding RelativeSource={RelativeSource Self}, Path=ViewModel}"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate x:Key="MyModel1Template1" DataType="{x:Type this:MyModel1}">
<StackPanel>
<TextBlock Text="Template 1"></TextBlock>
<ComboBox ItemsSource="{Binding Path=Values}" SelectedItem="{Binding Path=Field1}" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="MyModel1Template2" DataType="{x:Type this:MyModel1}">
<StackPanel>
<TextBlock Text="Template 2"></TextBlock>
<TextBox Text="{Binding Path=Field2}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<DockPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Top" Margin="2">
<ComboBox x:Name="TypeSelector">
<system:String>Template 1</system:String>
<system:String>Template 2</system:String>
</ComboBox>
</StackPanel>
<ContentControl Content="{Binding Path=Model}">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=TypeSelector, Path=SelectedItem}" Value="Template 2">
<Setter Property="ContentTemplate" Value="{StaticResource MyModel1Template2}" />
</DataTrigger>
</Style.Triggers>
<Setter Property="ContentTemplate" Value="{StaticResource MyModel1Template1}" />
</Style>
</ContentControl.Style>
</ContentControl>
</DockPanel>
</Window>
The notable points of the view are
The DataContext is initialised on the Window element, allowing for auto-complete on our binding expressions
The definition of 2 template to display 2 different views of the data.
The ComboBox is populated with a list of strings and has a default selection of the first element.
The ContentControl has its content bound to the Model exposed via the ViewModel
The default DataTemplate is the first template with a ComboBox.
The Trigger in the ContentControl's style will change the ContentTemplate if the SelectedItem of the ComboBox is changed to 'Template 2'
Implied facts are
If the SelectedItem changes back to 'Template 1', the style will revert the the ContentTemplate back to the default, ie MyModel1Template1
If there were a need for 3 separate displays, create another DataTemplate, add a string to the ComboBox and add another DataTrigger.
NOTE: This is the complete source to my example. Create a new C#/WPF project with the same classes and past the code in. It should work.
I hope this helps.

Adding UserControl dynamically on User interface

In my WPF application, I need to post n number of my user control in in form of rows and columns. My user control is like
The number of rows can be n, while number of columns will be either 1 or 2 depending on the view user chooses to use.
Here is the collection that contains my UserControls
private Collection<TemplateView> _templates;
public Collection<TemplateView> Templates { get { return _templates; } set { _templates = value; } }
And here is the XAML code that I used.
<ItemsControl ItemsSource="{Binding Templates}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="{Binding NumColumns}" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Grid.Column" Value="{Binding ColumnIndex}" />
<Setter Property="Grid.Row" Value="{Binding RowIndex}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<v:TemplateView Content="{Binding }" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
v:TemplateView is the UserControl whose n copied needs to be posted in rows/columns.
Here is some of the XAML showing binding of UserControl's controls.
<Label Content="{Binding Title, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
<Label Content="{Binding Type, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
<TextBlock><Hyperlink Command="{Binding DetailsViewCommand}">Details</Hyperlink>
</TextBlock>
<TextBlock><Hyperlink Command="{Binding AddCommand}">Add</Hyperlink>
And here is my UserControl's VIewModel's code
private ICommand _detailsViewCommand;
public ICommand DetailsViewCommand { get { return _detailsViewCommand ?? (_detailsViewCommand = new RelayCommand(DetailsView)); } }
public void DetailsView()
{
}
private ICommand _addCommand;
public ICommand AddCommand { get { return _addCommand ?? (_addCommand = new RelayCommand(Add)); } }
private void Add()
{
}
private string _layerType;
public string LayerType
{
get { return _layerType; }
set { _layerType = value; }
}
private string _title;
public string Title
{
get { return _title; }
set { _title = value; }
}
All copies of this UserControl will carry different information in labels and Image. So I need to know in the userControl's ViewModel, which UserControls (or which item in Templates Collection) has been clicked when user presses the Details button.
Above XAML and code does not tell me which item/user control was clicked on Details button click. So how should I accomplish the two tasks?

Load UserControl in TabItem

I have a Usercontrol(TabUserControl) which contains a TabControl. The Viewmodel of that UserControl loads ab Observable collection of TabItems. One od those items is another user control. When I just load text in the tabcontrol there is no problem, but how can I load the other user control into the tabitem of the TabUserControl.
I'm using MVVM.
Here's my code:
public class TabItem
{
public string Header { get; set; }
public object Content { get; set; } // object to allow all sort of items??
}
The Viewmodel of the TabUserControl
public class TabViewModel
{
public ObservableCollection<TabItem> Tabs {get;set;}
public TabViewModel()
{
Tabs = new ObservableCollection<TabItem>();
//Tabs.Add(new TabItem { Header = "Overview", Content = new OverviewViewModel() }); How to load a usercontrol here if it's in the ItemCollection?
Tabs.Add(new TabItem { Header = "Overview", Content = "Bla bla bla" });
Tabs.Add(new TabItem { Header = "Two", Content = "Two's content" });
}
}
And then the TabControl XAML:
<TabControl x:Name="_tabControl"
ItemsSource="{Binding Tabs}">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header"
Value="{Binding Header}" />
<Setter Property="Content"
Value="{Binding Content}" />
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
It works as long as I dont load the viewmodel of the usercontrol in the tabItems collection. how can I make the UserTabControl load on to the TabItem?
The intention is that every tabitem will contain a usercontrol. Each usercontrol then does it's own thing.
Hope someone can help me as I am a WPF beginner.
Thx!
Ideally, the TabControl.ItemsSource should be set to a collection of ViewModels, and DataTemplates should be used to tell the WPF to draw each ViewModel with a specific UserControl.
This keeps between your business logic (ViewModels) completely separate from your UI (Views)
For example,
<TabControl x:Name="MyTabControl"
ItemsSource="{Binding TabViewModels}"
SelectedItem="{Binding SelectedTabViewModel}">
<TabControl.Resources>
<DataTemplate DataType="{x:Type my:ViewModelA}">
<my:ViewAUserControl />
</DataTemplate>
<DataTemplate DataType="{x:Type my:ViewModelB}">
<my:ViewBUserControl />
</DataTemplate>
<DataTemplate DataType="{x:Type my:ViewModelC}">
<my:ViewCUserControl />
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Header}" />
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
ViewModel containing TabControl's DataContext:
TabViewModels = new ObservableCollection<ITabViewModel>();
TabViewModels.Add(new ViewModelA { Header = "Tab A" });
TabViewModels.Add(new ViewModelB { Header = "Tab B" });
TabViewModels.Add(new ViewModelC { Header = "Tab C" });
SelectedTabViewModel = TabViewModels[0];
Thanks Rachel for your answer. But it enforces declaring the DataContext during compile time itself. Like you did, relating each of the Views to their respective ViewModels in the DataTemplate of TabControl. We can achieve dynamic View-ViewModel linking when move this out to ViewModel. Here's how:
XAML:
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Header}" />
<Setter Property="Content" Value="{Binding Content}" />
</Style>
</TabControl.ItemContainerStyle>
VM:
public ObservableCollection<TabItem> TabItems { get; set; }
public MainWindowViewModel()
{
TabItems = new ObservableCollection<TabItem>
{
new TabItem{Content = new TabAView() {DataContext = new TabAViewModel()}, Header = "Tab A"},
new TabItem{Content = new TabBView(), Header = "Tab B"}
};
}
We can even make use of Action delegates to delay and invoke initialization of the TabItems only upon Tab SelectionChangedEvent. This achieves lot of memory saving if the UserControl Views have many UI elements.

When setting contextmenu via style setter, PlacementTarget property is null

In my application, I have a view (ListView) and a view model. Inside view model, I have 2 properties: first is a list of items, and the second is a command. I want to display items (from first property) inside ListView. In addition, I want to have for each one a context menu, where clicking on it will activate a command (from second property).
Here is a code of my view model:
public class ViewModel
{
public IEnumerable Items
{
get
{
return ...; //returns a collection of items
}
}
public ICommand MyCommand //this is a command, I want to be able execute from context menu of each item
{
get
{
return new DelegateCommand(new Action<object>(delegate(object parameter)
{
//here code of the execution
}
), new Predicate<object>(delegate(object parameter)
{
//here code of "can execute"
}));
}
}
Now the XAML part:
<ListView ItemsSource="{Binding Items}">
<ListView.Resources>
<commanding:CommandReference x:Key="myCommand" Command="{Binding MyCommand}"/>
</ListView.Resources>
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock
Text="{Binding Name}"
/>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem
Header="Remove from workspace"
Command="{StaticResource myCommand}"
CommandParameter="HERE I WANT TO PASS THE DATA CONTEXT OF THE ListViewItem"
/>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
</ListView>
The problem: until I actually opening context menu, the PlacementTarget of the context menu is null. I need somehow to receive data context of the clicked ListViewItem into "CanExecute" of the command, BEFORE the command being called - and I truly wish to make everything in the XAML, without handling any callbacks in code behind.
Thank you in advance.
If you are looking for ListViewItem's DataContext you can do this:
CommandParameter="{Binding}"
Edit - Here is what I tried:
public partial class MainWindow : Window
{
private ObservableCollection<Person> list = new ObservableCollection<Person>();
public MainWindow()
{
InitializeComponent();
list.Add(new Person() { Name = "Test 1"});
list.Add(new Person() { Name = "Test 2"});
list.Add(new Person() { Name = "Test £"});
list.Add(new Person() { Name = "Test 4"});
this.DataContext = this;
}
public static ICommand MyCommand //this is a command, I want to be able execute from context menu of each item
{
get
{
return new DelegateCommand<Person>(
a => Console.WriteLine(a.Name),
a => true);
}
}
public ObservableCollection<Person> Items
{
get
{
return this.list;
}
}
}
public class Person
{
public string Name { get; set; }
}
And the xaml:
<Window x:Class="ListView1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ListView1="clr-namespace:ListView1" Title="MainWindow" Height="350" Width="525">
<Grid>
<ListView ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="Remove from workspace" Command="{x:Static ListView1:MainWindow.MyCommand}" CommandParameter="{Binding}" />
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
</ListView>
</Grid>
</Window>

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