WPF: Setting ListView View via DataTrigger - c#

I have a list view and 2 resources for display the list's view: BooksGridView & ImageDetailView.
The ViewModel has a string property named ViewMode, which contains the name of the view i currently want to display. (It is changed from another control, using toolbars)
I am trying to change the selected view by using DataTrigger, but I cant seem to get the View property to change.
When i set the View resource directly, the correct view is displayed. I also added background changes to make sure the data trigger is activated, and the background did change.
So I'm obviously missing something here...
<UserControl x:Class="eLibrary.View.FilteredBooksView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:Converters="clr-namespace:eLibrary.Converters"
xmlns:Controls="clr-namespace:eLibrary.Controls"
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
xmlns:local="clr-namespace:eLibrary"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<Converters:CoverImageConverter x:Key="CoverImageConverter"/>
<BooleanToVisibilityConverter x:Key="BoolToVisConverter" />
<GridView x:Key="BooksGridView">
...
</GridView>
<Controls:TileView x:Key="ImageDetailView">
...
</Controls:TileView>
<CollectionViewSource x:Key="sortedBooks" Source="{Binding Books}">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="Title" Direction="Ascending"/>
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
<Style TargetType="{x:Type ListView}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=ViewMode}" Value="BooksGridView">
<Setter Property="View" Value="{StaticResource BooksGridView}"/>
<Setter Property="Background" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=ViewMode}" Value="ImageDetailView">
<Setter Property="View" Value="{StaticResource ImageDetailView}" />
<Setter Property="Background" Value="Blue"/>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<ListView
VerticalAlignment="Stretch"
Name="BooksListView"
View="{StaticResource BooksGridView}"
SelectionMode="Extended"
ItemsSource="{Binding Source={StaticResource sortedBooks}}">
<ListView.Resources>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}" />
<Setter Property="Visibility" Value="{Binding Path=ShouldDisplay, Converter={StaticResource BoolToVisConverter} }" />
</Style>
</ListView.Resources>
</ListView>
</UserControl>
Thanks

Based off the sample on MSDN, the following works at changing the view based on a change in the ViewModel. The only difference I can see with your code is the use of DynamicResource:
<Window x:Class="SDKSample.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Custom View"
xmlns:l="clr-namespace:SDKSample"
Width="400" Height="500"
SourceInitialized="Window_SourceInitialized">
<Window.Resources>
<DataTemplate x:Key="centralTile">
<StackPanel Height="100" Width="90">
<Grid Width="70" Height="70" HorizontalAlignment="Center">
<Image Source="{Binding XPath=#Image}" Margin="6,6,6,9"/>
</Grid>
<TextBlock Text="{Binding XPath=#Name}" FontSize="13"
HorizontalAlignment="Center" Margin="0,0,0,1" />
<TextBlock Text="{Binding XPath=#Type}" FontSize="9"
HorizontalAlignment="Center" Margin="0,0,0,1" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="iconTemplate">
<DockPanel Height="33" Width="150">
<Image Source="{Binding XPath=#Image}" Margin="2"/>
<TextBlock DockPanel.Dock="Top" Text="{Binding XPath=#Name}"
FontSize="13" HorizontalAlignment="Left"
Margin="0,0,0,1" />
<TextBlock Text="{Binding XPath=#Type}" FontSize="9"
HorizontalAlignment="Left" Margin="0,0,0,1" />
</DockPanel>
</DataTemplate>
<DataTemplate x:Key="checkbox">
<CheckBox IsChecked="{Binding IsSelected, RelativeSource= {RelativeSource AncestorType=ListViewItem}}"
Margin="0,1,1,1" >
</CheckBox>
</DataTemplate>
<XmlDataProvider x:Key="myXmlDataBase" XPath="/myXmlData">
<x:XData>
<myXmlData xmlns="">
<Item Name = "Fish" Type="fish" Image="images\fish.png"/>
<Item Name = "Dog" Type="animal" Image="images\dog.png"/>
<Item Name = "Flower" Type="plant" Image="images\flower.jpg"/>
<Item Name = "Cat" Type="animal" Image="images\cat.png"/>
</myXmlData>
</x:XData>
</XmlDataProvider>
<DataTemplate x:Key="DisplayImage">
<StackPanel Width="50">
<Image Source="{Binding XPath=#Image}"/>
</StackPanel>
</DataTemplate>
<GridView x:Key="gridView">
<GridViewColumn CellTemplate="{StaticResource checkbox}"/>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding XPath=#Name}"/>
<GridViewColumn Header="Type" DisplayMemberBinding="{Binding XPath=#Type}"/>
<GridViewColumn Header="Image" CellTemplate="{StaticResource DisplayImage}"/>
</GridView>
<l:PlainView x:Key="tileView" ItemTemplate="{StaticResource centralTile}" ItemWidth="100"/>
<l:PlainView x:Key="iconView" ItemTemplate="{StaticResource iconTemplate}" ItemWidth="150"/>
<Style TargetType="{x:Type ListView}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=ViewName}" Value="iconView">
<Setter Property="View" Value="{DynamicResource iconView}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=ViewName}" Value="tileView">
<Setter Property="View" Value="{DynamicResource tileView}" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=ViewName}" Value="gridView">
<Setter Property="View" Value="{DynamicResource gridView}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<StackPanel>
<ListView Name="lv"
ItemsSource="{Binding Source={StaticResource myXmlDataBase}, XPath=Item}"
FontSize="12"
Background="LightBlue" >
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="gridView" Click="SwitchViewMenu"/>
<MenuItem Header="iconView" Click="SwitchViewMenu"/>
<MenuItem Header="tileView" Click="SwitchViewMenu"/>
</ContextMenu>
</ListView.ContextMenu>
</ListView>
<TextBlock FontSize="16" Foreground="Blue">
CurrentView: <TextBlock Name="currentView" Text="{Binding Path=ViewName}"/>
</TextBlock>
<TextBlock>
Right-click in the content window to change the view.
</TextBlock>
</StackPanel>
</Window>
Code behind file:
using System;
using System.Windows;
using System.Windows.Controls;
namespace SDKSample
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
public MainViewModel ViewModel
{
get { return this.DataContext as MainViewModel; }
}
void SwitchViewMenu(object sender, RoutedEventArgs args)
{
MenuItem mi = (MenuItem)sender;
ViewModel.ViewName = mi.Header.ToString();
}
private void Window_SourceInitialized(object sender, EventArgs e)
{
ViewModel.ViewName = "gridView";
}
}
}
And finally the ViewModel class:
using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
namespace SDKSample
{
public class MainViewModel : INotifyPropertyChanged
{
public string ViewName
{
get { return viewName; }
set
{
if (viewName == value)
return;
viewName = value;
NotifyPropertyChanged("ViewName");
}
}
private string viewName;
public event PropertyChangedEventHandler PropertyChanged;
void NotifyPropertyChanged(string name)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}

I cannot see any obvious issue with your code you provided. I would usually suggest checking that the DataTrigger is being trigger, however you have already tested that with the Background property.
Looking at sample on MSDN (link) the only difference in implementation is that, in the sample, switch the ListView.View is changed in code.
BookListView.View = this.FindResource("BooksGridView") as ViewBase;
...
Hmm, perhaps the view resource may not be able to found and it is failing.
...
All I can suggest is look at the sample. Sorry couldn't be much more help.

Related

Button inside ListView not respond to ItemClick

I have three buttons inside ListView.
I tried to click each of the button, however the ItemClick function was not triggered.
There is the xaml file of the ListView
<UserControl.Resources>
<DataTemplate x:Name="DefaultWideSideItemTemplate" x:DataType="ui:AppMenuItem">
<Grid>
<ui:ButtonWithIcon IconContent="{x:Bind Icon}"
Content="{x:Bind Name}"
Style="{StaticResource TransparentFontButtonStyle}"
/>
</Grid>
</DataTemplate>
<others:SideMenuItemTemplateSelector DefaultTemplate="{StaticResource DefaultWideSideItemTemplate}" x:Key="WideSideItemTemplateSelector"/>
</UserControl.Resources>
<ListView Style="{StaticResource HorizontalCenterListView}"
IsItemClickEnabled="True"
ItemsSource="{x:Bind MenuItemCollection}"
VerticalAlignment="Top"
x:Name="SideMenuListView"
Loaded="SideMenuListView_Loaded"
ItemClick="SideMenuListView_ItemClick"
ItemTemplateSelector="{StaticResource WideSideItemTemplateSelector}">
</ListView>
There is the code where ItemClick was defined.
public event EventHandler<AppMenuItem> SideMenuItemClick;
private void SideMenuListView_ItemClick(object sender, ItemClickEventArgs e)
{
// DO SOMETHING HERE
}
There is the xaml file of the ui:ButtonWithIcon
<Style x:Key="TransparentFontButtonStyle" TargetType="ui:ButtonWithIcon">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="FontSize" Value="30"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="Margin" Value="0,32,0,7"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ui:ButtonWithIcon">
<StackPanel>
<TextBlock
x:Name="IconContent"
Text="{Binding IconContent, RelativeSource={RelativeSource TemplatedParent}}"
HorizontalAlignment="Center"
FontFamily="{StaticResource SymbolThemeFontFamily}"
FontSize="30"/>
<TextBlock
x:Name="Text"
Text="{TemplateBinding Content}"
HorizontalAlignment="Center"
FontSize="14"
Margin="0,10,0,0"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
What did I understand wrong about ListView? Buttons can not be used in ListView?
Update: ButtonWithIcon
public class ButtonWithIcon : Button
{
public static readonly DependencyProperty IconContentProperty =
DependencyProperty.Register("IconContent", typeof(string), typeof(ButtonWithIcon), new PropertyMetadata(default));
public string IconContent
{
get { return (string)GetValue(IconContentProperty); }
set { SetValue(IconContentProperty, value); }
}
}
Update 2:
<DataTemplate x:Name="DefaultWideSideItemTemplate" x:DataType="ui:AppMenuItem">
<Grid>
<StackPanel Margin="0,32,0,7">
<TextBlock Text="{x:Bind Icon}"
HorizontalAlignment="Center"
FontFamily="{StaticResource SymbolThemeFontFamily}"
FontSize="30"/>
<TextBlock
Text="{x:Bind Name}"
HorizontalAlignment="Center"
Margin="0,10,0,0"
FontSize="14"/>
</StackPanel>
</Grid>
</DataTemplate>
If the ui:ButtonWithIcon is a custom button control, then this is expected behavior. When you click on the custom button, the click event is captured by the button, so the ListView won't trigger the ItemClick event. A simple way to confirm is that you could click on the place that does not belong to the custom button. It should fire the ItemClick event of the ListView.
Update:
Since your code is too messy, I made a simple test using some of your code:
<ListView HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
IsItemClickEnabled="True"
ItemsSource="{x:Bind MenuItemCollection}"
x:Name="SideMenuListView"
Loaded="SideMenuListView_Loaded"
ItemClick="SideMenuListView_ItemClick"
>
<ListView.ItemTemplate>
<DataTemplate >
<Grid>
<StackPanel Margin="0,32,0,7">
<TextBlock
Text="{Binding}"
HorizontalAlignment="Center"
Margin="0,10,0,0"
FontSize="14"/>
</StackPanel>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
This works correctly.
You could remove the extra parts of your code to narrow down the issue. Start with a easy sample like the above.

WANTED Two independent instances of same user control as TabItem content (section "WPF is disrespecting my code")

Uncheck the checked one, select tab "Header B" and you'll see: the unchecking has been reflected.
How can WPF think I want one instance if I place two user controls and name them differently?
WPF is driving me mad over time.
MainWindow.xaml:
<!-- The silver price is currently high? I don't care. -->
<Window x:Class="WpfApplication2.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:UserControls="clr-namespace:WpfApplication2"
mc:Ignorable="d"
Background="Silver" Title="MainWindow" FontSize="14" WindowStartupLocation="CenterScreen"
Height="300" Width="700" FocusManager.FocusedElement="{Binding ElementName=_btCancel}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TabControl x:Name="_tabControl" Grid.Row="0" >
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Header}" />
<Setter Property="Content" Value="{Binding Content}" />
</Style>
</TabControl.ItemContainerStyle>
<TabItem x:Name="_tabItem_A" Header="Header A">
<TabItem.Content>
<Grid>
<UserControls:UserControl_1 x:Name="_userControl_A" />
</Grid>
</TabItem.Content>
</TabItem>
<TabItem x:Name="_tabItem_B" Header="Header B">
<TabItem.Content>
<Grid>
<UserControls:UserControl_1 x:Name="_userControl_B" />
</Grid>
</TabItem.Content>
</TabItem>
</TabControl>
<Button Name="_btCancel" Grid.Row="1" Content=" Cancel " Margin="0,10,0,10"
VerticalAlignment="Bottom" HorizontalAlignment="Center" IsCancel="True" Click="_btCancel_Click" />
</Grid>
</Window>
MainWindow.xaml.cs:
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication2
{
public partial class MainWindow : Window
{
public MainWindow()
{
List<_ListViewItem> listViewItems = new List<_ListViewItem>();
listViewItems.Add(new _ListViewItem() { _IsActive = false, _Text = "Text 1" });
listViewItems.Add(new _ListViewItem() { _IsActive = true, _Text = "Text 2" });
listViewItems.Add(new _ListViewItem() { _IsActive = false, _Text = "Text 3" });
Application.Current.Resources.Add("_xamlReference_listViewContent", listViewItems);
InitializeComponent();
}
private void _btCancel_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
}
public class _ListViewItem
{
public bool _IsActive { get; set; }
public string _Text { get; set; }
}
}
UserControl_1.xaml:
<UserControl x:Class="WpfApplication2.UserControl_1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<UserControl.Resources>
<CollectionViewSource x:Key="_collectionViewSource" Source="{Binding Source={StaticResource _xamlReference_listViewContent}}" />
</UserControl.Resources>
<Grid x:Name="_grid">
<ListView x:Name="_lv" Margin="10" ItemsSource="{Binding Source={StaticResource _collectionViewSource}, Mode=OneWay}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Margin" Value="0,5,0,0" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView>
<GridViewColumn Width="70">
<GridViewColumn.Header>
<Label Content="Active" />
</GridViewColumn.Header>
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Path=_IsActive}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="130">
<GridViewColumn.Header>
<Label Content="Interval" />
</GridViewColumn.Header>
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=_Text}" HorizontalContentAlignment="Center" Width="80" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Grid>
</UserControl>
UserControl_1.xaml.cs:
using System.Windows.Controls;
namespace WpfApplication2
{
public partial class UserControl_1 : UserControl
{
public UserControl_1()
{
InitializeComponent();
}
}
}
App.xaml:
<Application x:Class="WpfApplication2.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication2"
xmlns:UserControls="clr-namespace:WpfApplication2"
StartupUri="MainWindow.xaml">
<Application.Resources>
<!-- These brute force statements do not help: -->
<Style TargetType="{x:Type UserControls:UserControl_1}" x:Shared="False" />
<Style TargetType="{x:Type ListView}" x:Shared="False" />
</Application.Resources>
</Application>
The problem here is that both UserControls are using the same List<_ListViewItem> as their resource in this code:
<UserControl.Resources>
<CollectionViewSource x:Key="_collectionViewSource" Source="{Binding Source={StaticResource _xamlReference_listViewContent}}" />
</UserControl.Resources>
when you do this the Checkbox on both controls is bound to the same _IsActive property of the same object, so changing one will change the other. One way to fix this (see comments) is to set the binding on the check box to OneWay like this:
<CheckBox IsChecked="{Binding Path=_IsActive, Mode=OneWay}" />
Use MVVM pattern. Have two view-models (one for each tab) that share the state that's related to the checking of the checkboxes and bind the TabItem DataContext to these view-models.
Generally speaking you should avoid WPF magic when you can and use bindings/view-models to solve your problems.

Dynamically use different UserControl based on datacontext

I have a chunk of xaml that duplicates the same pattern six times and would like to reduce that footprint by eliminating the duplication. I'm running into one snag that I need help with.
Background: I have a class that instantiates another class six times (phases of a project as you will).
public ECN(string ecnNumber) {
_ECNNumber = ecnNumber;
//instantiate each phase to be populated or not
_PurchaseParts = new ECNPhase();
_PieceParts = new ECNPhase();
_Weldments = new ECNPhase();
_BOMCfg = new ECNPhase();
_Cleanup = new ECNPhase();
_PRAF = new ECNPhase();
}
Inside each phase is a collection of Changes (another class) referenced in the ECNPhase Class. Each phase has data that is unique to each phase that is shown in a unique view, this is where my snag is which I will show later.
Example of the duplicate xaml Code with the main difference being the different view inside each expander:
<StackPanel Margin="0">
<!--Section for Purchase parts-->
<StackPanel Orientation="Horizontal" >
<CheckBox Margin="0,5,5,5" IsChecked="{Binding Path=MyWorkspace.CurrentSelectedItem.PurchaseParts.HasPhase,Mode=TwoWay}"/>
<StackPanel Orientation="Horizontal">
<StackPanel.Style>
<Style TargetType="{x:Type StackPanel}">
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=MyWorkspace.CurrentSelectedItem.PurchaseParts.HasPhase}" Value="True">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<Expander Header="Purchase Parts" Margin="0,0,10,0" Width="110">
<view:PurchasePartsView/>
</Expander>
<CheckBox Content="Submit" Margin="10,5,0,5"/> <!--add a command to handle the submit checkbox event-->
<Label Content="Status:" Margin="10,0,0,0" HorizontalContentAlignment="Right" Width="60"/>
<Label Content="{Binding Path=MyWorkspace.CurrentSelectedItem.PurchaseParts.Status}"/>
</StackPanel>
</StackPanel>
<!--Section for Piece Parts-->
<StackPanel Orientation="Horizontal">
<CheckBox Margin="0,5,5,5" IsChecked="{Binding Path=MyWorkspace.CurrentSelectedItem.PieceParts.HasPhase,Mode=TwoWay}"/>
<StackPanel Orientation="Horizontal">
<StackPanel.Style>
<Style TargetType="{x:Type StackPanel}">
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=MyWorkspace.CurrentSelectedItem.PieceParts.HasPhase}" Value="True">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<Expander Header="Piece Parts" Margin="0,0,10,0" Width="110">
<view:PiecePartsView/>
</Expander>
<CheckBox Content="Submit" Margin="10,5,0,5"/> <!--add a command to handle the submit checkbox event-->
<Label Content="Status:" Margin="10,0,0,0" HorizontalContentAlignment="Right" Width="60"/>
<Label Content="{Binding Path=MyWorkspace.CurrentSelectedItem.PieceParts.Status}"/>
</StackPanel>
</StackPanel>
<!--duplicated four more times-->
</StackPanel>
What I'd like to do is:
<StackPanel>
<view:PhaseView DataContext="{Binding Path=MyWorkspace.CurrentSelectedItem.PurchaseParts}"/>
<view:PhaseView DataContext="{Binding Path=MyWorkspace.CurrentSelectedItem.PieceParts}"/>
<!--four more phases-->
</StackPanel>
Where the PhaseView will be the template that handles the duplication and this is where I'm hitting a snag. Each phase needs a unique view (userControl) selected based off of the datacontext of the PhaseView.
<StackPanel Orientation="Horizontal" >
<CheckBox Margin="0,5,5,5" IsChecked="{Binding Path=HasPhase,Mode=TwoWay}"/>
<StackPanel Orientation="Horizontal">
<StackPanel.Style>
<Style TargetType="{x:Type StackPanel}">
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=HasPhase}" Value="True">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<Expander Header="DisplayName" Margin="0,0,10,0" Width="110">
<!--add somthing here to select the correct view based on the datacontext-->
<!--<local:PurchasePartsView/> This user control adds a datagrid that is unique to each phase-->
</Expander>
<CheckBox Content="Submit" Margin="10,5,0,5"/> <!--add a command to handle the submit checkbox event-->
<Label Content="Status:" Margin="10,0,0,0" HorizontalContentAlignment="Right" Width="60"/>
<Label Content="{Binding Path=Status}"/>
</StackPanel>
</StackPanel>
I was thinking of using a datatrigger somehow lik shown below, but I haven't had any luck figuring it out. I know there's got to be a way to do this, and it's probably something simple and dumb. Any help would be much appreciated.
<DataTrigger Binding="{Binding Path=DisplayName}" Value="Purchase Parts">
<Setter Property="DataContext" Value="{Binding }"/> <!--Don't know how to bind the DataContext-->
</DataTrigger>
Thanks,
Ok, thanks to Bradley I looked into the DataTemplateSelector and this is what I came up with.
In my UserControl resources I set up several DataTemplates and a reference to the TemplateSelector class that overides the DataTemplateSelector class.
XAML Resources:
<UserControl.Resources>
<local:TemplateSelector x:Key="myTemplateSelector"/>
<DataTemplate x:Key="PurchasePartsTemplate">
<view:PurchasePartsView/>
</DataTemplate>
<DataTemplate x:Key="PiecePartsTemplate">
<view:PiecePartsView/>
</DataTemplate>
<!--Four more templates-->
</UserControl.Resources>
Code Behind for DataTemplateSelector override. Note: I couldn't figure out a way to bind to the ECNPhase class so I bound to the DisplayName property in my class to pull out the correct instance being represented.
class TemplateSelector : DataTemplateSelector {
public override DataTemplate SelectTemplate(object item, DependencyObject container) {
FrameworkElement element = container as FrameworkElement;
if(element != null && item != null && item is string) {
string phase = (string)item;
if(phase == "Purchase Parts") {
return element.FindResource("PurchasePartsTemplate") as DataTemplate;
}else if(phase == "Piece Parts") {
return element.FindResource("PiecePartsTemplate") as DataTemplate;
}
}
return null;
}
}
I'm calling this class in my UserContol like this:
<Expander Header="{Binding Path=DisplayName}" Margin="0,0,10,0" Content="{Binding Path=DisplayName}"
ContentTemplateSelector="{StaticResource myTemplateSelector}"/>
There isn't an items control associated with the expander so I used the content control. I pass the DisplayName into the control property and the contentTemplateSelector uses the myTemplateSelector resource which goes into the codebehind and selects the appropriate datatemplate to use based on the DisplayName.
Now I can call my reusable template like so:
<StackPanel Margin="0">
<view:ChangePhaseView DataContext="{Binding Path=MyWorkspace.CurrentSelectedItem.PurchaseParts}"/>
<view:ChangePhaseView DataContext="{Binding Path=MyWorkspace.CurrentSelectedItem.PieceParts}"/>
</StackPanel>
#Bradley, thank you for pointing me in the right direction.

ComboBox selected item data triggers to collapse button on child user control

I am trying to figure out how to get data triggers to work between user controls - either between a window and a child user control (a user control embedded in the window), or between a user control that has a child user control.
The button control has 5 buttons but by default the 5th button is collapsed. When the combobox item "Fifth Button" is selected I want the Fourth button to collapse and the Fifth button to become visible. As you can see I have the triggers set to update the Label on the Mainwindow based on the combobox selection. I have no issue using triggers within the same window but I don't know how to make them work to communicate to a user control that is embedded in the same window. Or from one control to another.
<Window x:Class="ComboboxControlChange.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ComboboxControlChange"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<ComboBox Grid.Row="0" x:Name="ButtonSelectCombobox" SelectedValuePath="Content" SelectedValue="{Binding ButtonSelection}" Height="24" Margin="150,0">
<ComboBoxItem x:Name="FirstButtonSelection" >First Button</ComboBoxItem>
<ComboBoxItem x:Name="SecondButtonSelection">Second Button</ComboBoxItem>
<ComboBoxItem x:Name="ThirdButtonSelection">Third Button</ComboBoxItem>
<ComboBoxItem x:Name="FourthButtonSelection">Fourth Button</ComboBoxItem>
<ComboBoxItem x:Name="FifthButtonSelection">Fifth Button</ComboBoxItem>
</ComboBox>
<StackPanel Grid.Row="1" VerticalAlignment="Center" HorizontalAlignment="Center" Orientation="Vertical">
<Label>You have selected button:</Label>
<Label HorizontalAlignment="Center">
<Label.Style>
<Style TargetType="{x:Type Label}">
<Setter Property="Content" Value=""/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=FirstButtonSelection, Path=IsSelected}" Value="true">
<Setter Property="Content" Value="One" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=SecondButtonSelection, Path=IsSelected}" Value="true">
<Setter Property="Content" Value="Two" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=ThirdButtonSelection, Path=IsSelected}" Value="true">
<Setter Property="Content" Value="Three" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=FourthButtonSelection, Path=IsSelected}" Value="true">
<Setter Property="Content" Value="Four" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=FifthButtonSelection, Path=IsSelected}" Value="true">
<Setter Property="Content" Value="Five" />
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
</StackPanel>
</Grid>
</Grid>
<Grid Grid.Row="1">
<local:ButtonControl />
</Grid>
</Grid>
</Window>
<UserControl x:Class="ComboboxControlChange.ButtonControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ComboboxControlChange"
mc:Ignorable="d"
d:DesignHeight="160" d:DesignWidth="517">
<Grid Name="Link1MainGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Grid.Column="0" VerticalAlignment="Center" Margin="5,0" >
<TextBlock TextAlignment="Center">
First<LineBreak/>Button
</TextBlock>
</Button>
<Button Grid.Column="1" VerticalAlignment="Center" Margin="5,0">
<TextBlock TextAlignment="Center">
Second<LineBreak/>Button
</TextBlock>
</Button>
<Button Grid.Column="2" VerticalAlignment="Center" Margin="5,0">
<TextBlock TextAlignment="Center">
Third<LineBreak/>Button
</TextBlock>
</Button>
<Button Grid.Column="3" VerticalAlignment="Center" Margin="5,0" >
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=FifthButtonSelected, Path=IsSelected}" Value="true">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
<TextBlock TextAlignment="Center">
Fourth<LineBreak/>Button
</TextBlock>
</Button>
<Button Grid.Column="3" Margin="4,4,4,50" Visibility="Collapsed">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=FifthButtonSelected, Path=IsSelected}" Value="true">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
<TextBlock TextAlignment="Center">
Fifth<LineBreak/>Button
</TextBlock>
</Button>
</Grid>
</UserControl>
I've tried binding the buttons with ElementName, Path, and even relativeSource but have't had any success. I've also tried adding the triggers in the ButtonControl.Resources section of the control.
<DataTrigger Binding="{Binding ElementName=FifthButtonSelected, Path=IsSelected}" Value="true">
<DataTrigger Binding="{Binding RelativeSource={RelatvieSource FindAncestorType={x:Type ComboBoxItem}}, Path=IsSelected}" Value="true">
Any help would be appreciated!
This won't work because the element names that are in the window will not be in scope for the user control. MSDN says:
[...] the primary XAML namescope is defined at the XAML root element of a
single XAML production, and encompasses the elements that are
contained in that XAML production.
What that means, practically, is that when you define an x:Name for an element in a file, it can only be referenced in that file, and will be unknown outside of it.
Another way to go about this would be to create a Dependency Property on the user control, and use that as a way to pass information between the window and the control. A nice side affect is that this creates some abstraction and allows for more flexibility.
ButtonControl.xaml.cs: (Rename 'Feature' to something relevant)
public partial class ButtonControl : UserControl
{
...
public bool IsFeatureVisible
{
get { return (bool)GetValue(IsFeatureVisibleProperty); }
set { SetValue(IsFeatureVisibleProperty, value); }
}
// Using a DependencyProperty as the backing store for IsFeatureVisible. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsFeatureVisibleProperty =
DependencyProperty.Register("IsFeatureVisible", typeof(bool), typeof(ButtonControl), new UIPropertyMetadata(false));
...
}
Now you could wire up the trigger to use this property, but we're lucky in this case that you're dealing with booleans and Visibility, so we can make it simpler:
ButtonControl.xaml:
<UserControl x:Class="ComboboxControlChange.ButtonControl"
x:Name="Myself"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ComboboxControlChange"
mc:Ignorable="d"
d:DesignHeight="160" d:DesignWidth="517">
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="mBooleanToVisibilityConverter"/>
</UserControl.Resources>
<Grid Name="Link1MainGrid">
...
<Button Grid.Column="3" VerticalAlignment="Center" Margin="5,0" Visibility="{Binding ElementName=Myself, Path=IsFeatureVisible, Converter={StaticResource mBooleanToVisibilityConverter}}">
<TextBlock TextAlignment="Center">
Fourth<LineBreak/>Button
</TextBlock>
</Button>
...
</Grid>
</UserControl>
Lastly, in the Window, we need to provide a value for that property on the instance of our button control. We can use the FifthButtonSelection element name here just like you are in other parts of the file:
MainWindow.xaml
<local:ButtonControl IsFeatureVisible="{Binding ElementName=FifthButtonSelection, Path=IsSelected}"/>

Binding to a dependency property of a behavior inside a child control

I have the following user control with a custom dependency property
ThumbnailListView UserControl
<ListView
ItemsSource="{Binding}"
BorderThickness="0,0,0,0"
HorizontalAlignment="Center"
Background="White"
SelectionChanged="ListView_SelectionChanged"
AllowDrop="True">
<i:Interaction.Behaviors>
<behaviors:DragDropBehavior OnDragDrop="{Binding Path=ItemDragDrop}"></behaviors:DragDropBehavior>
</i:Interaction.Behaviors>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource IsLastListItem}}" Value="False">
<Setter Property="Margin" Value="0,0,0,20"></Setter>
</DataTrigger>
</Style.Triggers>
<Setter Property="Background" Value="Gray"></Setter>
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}"></Setter>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Image Source="{Binding Thumbnail, Converter={StaticResource ImageConverter}}"></Image>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Dependency Property ItemDragDrop of ThumbnailListView
public static ICommand GetItemDragDrop(DependencyObject obj)
{
return (ICommand)obj.GetValue(ItemDragDropProperty);
}
public static void SetItemDragDrop(DependencyObject obj, ICommand value)
{
obj.SetValue(ItemDragDropProperty, value);
}
public static DependencyProperty ItemDragDropProperty = DependencyProperty.RegisterAttached("ItemDragDrop",
typeof(ICommand), typeof(ThumbnailListView));
public ICommand ItemDragDrop
{
get
{
return (ICommand)GetValue(ItemDragDropProperty);
}
set
{
SetValue(ItemDragDropProperty, value);
}
}
NewScansView UserControl
<DockPanel Dock="Top">
<ListView ItemsSource="{Binding Scans}" Width="500">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Caption}" Margin="5,0,0,0"></TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
<Views:ThumbnailListView DataContext="{Binding SelectedItem.Pages}" ItemDragDrop="{Binding SelectedItem.DragDropCommand}" VerticalAlignment="Stretch" Width="200" />
<Views:PageListView DataContext="{Binding SelectedItem.Pages}" VerticalAlignment="Stretch" />
</DockPanel>
The NewScansView xaml contains a ThumbnailListView control and has bound the dependency property ItemDragDrop of the ThumbnailListView to a command in the class of SelectedItem.
Inside the ThumbnailListView user control I have a behavior DragDropBehavior which has a dependency property OnDragDrop.
I am trying to bind OnDragDrop to ItemDragDrop so that when a drag drop operation completes the command in the SelectedItem class is executed.
The problem is that it doesn't seem to be able to find either the ItemDragDrop property on the ThumbnailListView or the DragDropCommand of the selected item class.
I'm wondering what i'm doing wrong and how I can set it up?
ThumbnailListView.DataContext is set to SelectedItem.Pages, and I highly doubt that SelectedItem.Pages has a property called SelectedItem.DragDropCommand.
Change the Source of your ItemDragDrop binding to specify it uses something other than the ThumnailListView.DataContext for the source of that binding.
<DockPanel x:Name="MyDockPanel" Dock="Top">
...
<Views:ThumbnailListView DataContext="{Binding SelectedItem.Pages}"
ItemDragDrop="{Binding SelectedItem.DragDropCommand, ElementName=MyDockPanel}" ... />
...
</DockPanel>
You probably also have to do the same for your behavior binding - change the source of it so it points to the ThumbnailListView instead of to the ThumbnailListView.DataContext
<i:Interaction.Behaviors>
<behaviors:DragDropBehavior OnDragDrop="{Binding Path=ItemDragDrop, RelativeSource={RelativeSource AncestorType={x:Type local:ThumbnailListView}}}" />
</i:Interaction.Behaviors>
Or better yet, either make a DependencyProperty for Pages so you don't rely on a specific DataContext object type
<Views:ThumbnailListView PagesDependencyProperty="{Binding SelectedItem.Pages}" ItemDragDrop="{Binding SelectedItem.DragDropCommand}" .. />
Or edit your control so it assumes a specific type is being used for the DataContext, and use an implicit DataTemplate to always draw that type of object using this control (far more common for me) :
<ListView ItemsSource="{Binding Pages}" ...>
<i:Interaction.Behaviors>
<behaviors:DragDropBehavior OnDragDrop="{Binding DragDropCommand}" />
</i:Interaction.Behaviors>
...
</ListView>
Implicit DataTemplate:
<DataTemplate DataType="{x:Type local:WhateverSelectedItemDataTypeIs}}">
<!-- DataContext will automatically set to WhateverSelectedItemDataTypeIs -->
<Views:ThumbnailListView />
</DataTemplate>

Categories

Resources