I'm having an issue with the content of a ContentControl whose content was set using DataTriggers randomly resetting the content to the default content specified in the DataTemplate.
The scenario is that I have a bunch of devices (sensors) out on the network that I need to check status on, among other things. Depending on what state the sensor is in I may want to show a colored circle (green, red or yellow), or an image. For example if the sensor is in use by someone I want to show an image representing a user. If the sensor is available for connection I want to show a green ellipse, etc.
I'm currently using a WPF DataGrid to display a list of sensors and their status, though I get the same erroneous behavior with a ListBox and ListView (haven't tried plain ItemsControl). FYI, sensors come and go asynchronously.
What you'll see if you run the sample code is that initially items with a connection state of CONNECTED will first display with the desired image. As rows get added to the grid the image randomly disappears and is replace with the default content specified in the DataTemplate. This problem only occurs when there is an image in the content. The other states work just fine.
Below is all the code (xaml, viewmodel, model) I think you'll need to see the behavior. Sorry for the amount of code posted below. I've tried to pair it down as much as possible to illustrate the issue. Hopefully the problem is obvious by looking at the XAML. The remaining source code will help you get it up and running more quickly if you so choose.
Here's the Window XAML:
<Window x:Class="StackOverflowGridIssue.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:model="clr-namespace:StackOverflowGridIssue.Model"
xmlns:shape="http://schemas.microsoft.com/expression/2010/drawing"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" Width="500"
Title="MainWindow" >
<Grid>
<DataGrid Name="_SensorsDataGrid" ItemsSource="{Binding Sensors}"
AutoGenerateColumns="False" HeadersVisibility="Column" >
<DataGrid.Columns>
<!-- Status -->
<DataGridTemplateColumn Header="Status" MinWidth="50"
Width="SizeToHeader" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ContentControl x:Name="myContent" Background="LimeGreen"
Width="25" Height="25">
<ContentControl.ToolTip>
<TextBlock Text="{Binding ConnectionState, Mode=OneWay}"
Foreground="Black" />
</ContentControl.ToolTip>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}" BasedOn="{x:Null}" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ContentControl}">
<Grid>
<Ellipse Height="10" Width="10"
Fill="{TemplateBinding Background}"
Stroke="{TemplateBinding Background}" />
<ContentPresenter />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ContentControl.Style>
</ContentControl>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding ConnectionState, Mode=OneWay}"
Value="{x:Static model:ConnectionStateType.NOT_FOUND}">
<Setter TargetName="myContent" Property="Background" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding ConnectionState, Mode=OneWay}"
Value="{x:Static model:ConnectionStateType.AVAILABLE}">
<Setter TargetName="myContent" Property="Background"
Value="GREEN" />
</DataTrigger>
<DataTrigger Binding="{Binding ConnectionState, Mode=OneWay}"
Value="{x:Static model:ConnectionStateType.CONNECTED}">
<Setter TargetName="myContent" Property="Content">
<Setter.Value>
<Image Source="Images/User.png" />
</Setter.Value>
</Setter>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!-- DEBUG Connection State -->
<DataGridTextColumn Header="DEBUG"
Binding="{Binding ConnectionState}" Width="SizeToCells" />
<!-- Sensor Name -->
<DataGridTextColumn Header="Sensor Name"
Binding="{Binding Name}" Width="SizeToCells" />
<!-- IPAddress -->
<DataGridTextColumn Header="IP Address"
Binding="{Binding IPAddress}" Width="SizeToCells" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
Here's the App.xaml.cs where I bootstrap everything and simulate sensors being discovered asynchronously (fyi, the same issue occurs if I load them serially, it's just easier to see when the sensors are loaded slowly one at a time:
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
public const int NUM_SENSORS = 100;
Random _connectionStateGenerator = new Random();
ConnectionStateType _connectionState = ConnectionStateType.AVAILABLE;
SensorViewModel viewModel;
Timer _timer = new Timer(300);
int _index = 1;
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
// Get a handle to the main view (MainWindow)
Window window = new MainWindow();
viewModel = new SensorViewModel();
//Loads sensors synchronously (same issue)
//AddSensors(viewModel.Sensors);
window.DataContext = viewModel;
window.Show();
//Simulate async sensor discovery (more real world example)
_timer.Enabled = false;
_timer.AutoReset = true;
_timer.Elapsed += (s, args) =>
{
Dispatcher.InvokeAsync(new Action( () =>
{
viewModel.Sensors.Add(
new Sensor("Sensor" + _index, "192.168.1." + _index,
(ConnectionStateType)_connectionStateGenerator.Next(0, 3)));
if (_index++ > NUM_SENSORS)
_timer.Enabled = false;
}));
};
_timer.Enabled = true;
}
//Helper for loading synchronously rather than asynchronously
private void AddSensors(ObservableCollection<Model.Sensor> sensors)
{
for (int i = 0; i < NUM_SENSORS; i++)
{
_connectionState = (ConnectionStateType)_connectionStateGenerator
.Next(0, 5);
sensors.Add(
new Sensor("Sensor" + i, "192.168.1." + i, _connectionState));
}
}
}
Here's the model code representing a sensor:
public enum ConnectionStateType
{
NOT_FOUND,
AVAILABLE,
CONNECTED,
}
public class Sensor : INotifyPropertyChanged
{
string _name = "Unknown";
string _IPAddress;
ConnectionStateType _connectionState = ConnectionStateType.AVAILABLE;
public Sensor(string name, string IPAddress, ConnectionStateType connectionState)
{
_name = name;
_IPAddress = IPAddress;
_connectionState = connectionState;
}
public ConnectionStateType ConnectionState
{
get { return _connectionState; }
set
{
if (value == _connectionState) return;
_connectionState = value;
NotifyPropertyChanged("ConnectionState");
}
}
public string Name
{
get { return _name; }
set
{
if (value == _name) return;
_name = value;
NotifyPropertyChanged("Name");
}
}
public string IPAddress
{
get { return _IPAddress; }
set
{
if (value == _IPAddress) return;
_IPAddress = value;
NotifyPropertyChanged("IPAddress");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string property)
{
var handler = PropertyChanged;
if(handler != null)
handler(this, new PropertyChangedEventArgs(property));
}
}
Here's the View Model:
public class SensorViewModel : INotifyPropertyChanged
{
ObservableCollection<Sensor> _sensors = new ObservableCollection<Sensor>();
public ObservableCollection<Sensor> Sensors
{
get { return _sensors; }
private set
{
if (value == _sensors) return;
_sensors = value;
NotifyPropertyChanged("Sensors");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string property)
{
var handler = PropertyChanged;
if(handler != null)
handler(this, new PropertyChangedEventArgs(property));
}
}
Thanks for your help.
The solution ended up being to have the trigger swap out the entire template rather than trying to set the Content area in the template.
Here is a cleaner piece XAML with template resources defined and a DataGrid that uses those resources:
<UserControl x:Class="StackOverflowGridIssue.SensorGrid"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:model="clr-namespace:StackOverflowGridIssue.Model"
xmlns:shape="http://schemas.microsoft.com/expression/2010/drawing"
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"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<ControlTemplate x:Key="DefaultConnectionStateTemplate"
TargetType="{x:Type ContentControl}">
<Grid>
<Ellipse Height="10" Width="10"
Fill="{TemplateBinding Background}"
Stroke="{TemplateBinding Background}" />
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</ControlTemplate>
<ControlTemplate x:Key="ConnectedTemplate"
TargetType="{x:Type ContentControl}" x:Shared="false">
<Grid>
<Image Source="Images/User.png" Height="25" Width="25"/>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</ControlTemplate>
<DataTemplate x:Key="SensorConnectionStateTemplate">
<ContentControl x:Name="myContent">
<ContentControl.ToolTip>
<TextBlock Text="{Binding ConnectionState, Mode=OneWay}" />
</ContentControl.ToolTip>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}" BasedOn="{x:Null}" >
<Setter Property="Template"
Value="{StaticResource DefaultConnectionStateTemplate}" />
</Style>
</ContentControl.Style>
</ContentControl>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding ConnectionState, Mode=OneWay}"
Value="{x:Static model:ConnectionStateType.NOT_FOUND}">
<Setter TargetName="myContent" Property="Background" Value="Red" />
<Setter TargetName="myContent" Property="Content" Value="Not Found" />
</DataTrigger>
<DataTrigger Binding="{Binding ConnectionState, Mode=OneWay}"
Value="{x:Static model:ConnectionStateType.AVAILABLE}">
<Setter TargetName="myContent" Property="Background" Value="GREEN" />
<Setter TargetName="myContent" Property="Content" Value="Available" />
</DataTrigger>
<DataTrigger Binding="{Binding ConnectionState, Mode=OneWay}"
Value="{x:Static model:ConnectionStateType.CONNECTED}">
<Setter TargetName="myContent" Property="Template"
Value="{StaticResource ConnectedTemplate}" />
<Setter TargetName="myContent" Property="Content" Value="Connected" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</UserControl.Resources>
<Grid>
<DataGrid Name="_SensorsDataGrid" AutoGenerateColumns="False"
ItemsSource="{Binding Sensors}"
HeadersVisibility="Column" >
<DataGrid.Columns>
<!-- Status -->
<DataGridTemplateColumn Header="Status" MinWidth="50"
Width="SizeToHeader" IsReadOnly="True"
CellTemplate="{StaticResource SensorConnectionStateTemplate}" />
<!-- DEBUG Connection State -->
<DataGridTextColumn Header="DEBUG"
Binding="{Binding ConnectionState}" Width="SizeToCells" />
<!-- Sensor Name -->
<DataGridTextColumn Header="Sensor Name"
Binding="{Binding Name}" Width="SizeToCells" />
<!-- IPAddress -->
<DataGridTextColumn Header="IP Address" Width="SizeToCells"
Binding="{Binding IPAddress}" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</UserControl>
Related
I need to change the view of a TabControl's content on-the-fly.
I am guessing the best way to accomplish this is to define the view as a DataTemplate, and then change said template using a trigger.
In my test app, the background color is tied to the same data trigger as the template. The background color updates immediately upon making the radio button selection.
Expected behavior: The Tab Item Content / DataTemplate also updates immediately.
Actual Behavior: Tab content view does not update until the tab selection is changed.
Here's my Minimal, Complete, and Verifiable example:
Window XAML
<Window x:Class="ChangeView.Window1"
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"
Title="Window1" Height="350" Width="400">
<Window.Resources>
<DataTemplate x:Key="ContentTemplate1">
<Grid>
<Label HorizontalAlignment="Center" VerticalAlignment="Center" Content="{Binding MyBlurb}"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="ContentTemplate2">
<Grid>
<Label HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
HorizontalContentAlignment="Center" VerticalContentAlignment="Center"
Content="{Binding MyHeader}" Background="Black" Foreground="White" FontSize="72"/>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.Style>
<Style TargetType="{x:Type Grid}">
<Style.Triggers>
<DataTrigger Binding="{Binding ViewType1}" Value="False">
<Setter Property="Background" Value="Chartreuse"/>
</DataTrigger>
<DataTrigger Binding="{Binding ViewType1}" Value="True">
<Setter Property="Background" Value="Bisque"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Margin="10,38,0,0" Text="Content Template:"/>
<RadioButton x:Name="radio1" Margin="120,40,0,0" Grid.ColumnSpan="2" Content="1" GroupName="ViewSelect" IsChecked="{Binding Path=ViewType1}"/>
<RadioButton Margin="170,40,0,0" Grid.ColumnSpan="2" Content="2" GroupName="ViewSelect"/>
<TabControl Grid.Row="1" ItemsSource="{Binding TabGroup}">
<TabControl.Style>
<Style TargetType="{x:Type TabControl}">
<Setter Property="Margin" Value="10"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ViewType1}" Value="True">
<Setter Property="ContentTemplate" Value="{DynamicResource ContentTemplate1}"/>
</DataTrigger>
<DataTrigger Binding="{Binding ViewType1}" Value="False">
<Setter Property="ContentTemplate" Value="{DynamicResource ContentTemplate2}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TabControl.Style>
<TabControl.ItemTemplate>
<DataTemplate>
<Border x:Name="headerBorder">
<Label Content="{Binding MyHeader}" FontSize="20"/>
</Border>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
</Grid>
</Window>
Code Behind
namespace ChangeView
{
using System.Windows;
using System.ComponentModel;
using System.Collections.ObjectModel;
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window, INotifyPropertyChanged
{
public ObservableCollection<TabData> TabGroup { get; set; } = new ObservableCollection<TabData>();
private bool _viewType1 = true;
public bool ViewType1
{
get { return _viewType1; }
set { _viewType1 = value; RaisePropertyChanged(nameof(ViewType1)); }
}
public Window1()
{
TabGroup.Add(new TabData("♻️", "Recycle"));
TabGroup.Add(new TabData("⚔", "Swords"));
TabGroup.Add(new TabData("⚗", "Chemistry"));
TabGroup.Add(new TabData("🌵", "Cactus"));
TabGroup.Add(new TabData("👺", "Tengu"));
TabGroup.Add(new TabData("🐙", "Octopus"));
DataContext = this;
InitializeComponent();
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private void RaisePropertyChanged(string propName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
public class TabData : INotifyPropertyChanged
{
private string _myHeader, _myBlurb;
public TabData(string header, string blurb)
{
MyHeader = header;
MyBlurb = blurb;
}
public string MyHeader
{
get { return _myHeader; }
set { _myHeader = value; RaisePropertyChanged(nameof(MyHeader)); }
}
public string MyBlurb
{
get { return _myBlurb; }
set { _myBlurb = value; RaisePropertyChanged(nameof(MyBlurb)); }
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private void RaisePropertyChanged(string propName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
After changing the radio button state, change the selected tab. You will then see the correct content template.
It looks as if, in a TabControl, changing the content template alone does not cause the content to be rendered. If you render new content by switching the selected tab, the current content template will then be used.
So let's write one ContentTemplate, which creates a ContentControl and switches the ContentControl's ContentTemplate. I've tested, and the ContentControl will re-render its content when its ContentTemplate changes. The bindings get a little bit verbose.
<TabControl ItemsSource="{Binding TabGroup}" Grid.Row="1">
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl
x:Name="ContentCtl"
Content="{Binding}"
/>
<DataTemplate.Triggers>
<DataTrigger
Binding="{Binding DataContext.ViewType1, RelativeSource={RelativeSource AncestorType=TabControl}}"
Value="True">
<Setter
TargetName="ContentCtl"
Property="ContentTemplate"
Value="{DynamicResource ContentTemplate1}"
/>
</DataTrigger>
<DataTrigger
Binding="{Binding DataContext.ViewType1, RelativeSource={RelativeSource AncestorType=TabControl}}"
Value="False"
>
<Setter
TargetName="ContentCtl"
Property="ContentTemplate"
Value="{DynamicResource ContentTemplate2}"
/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</TabControl.ContentTemplate>
<TabControl.ItemTemplate>
<DataTemplate>
<Border x:Name="headerBorder">
<Label Content="{Binding MyHeader}" FontSize="20"/>
</Border>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
You could also do something ugly in your code behind to make the TabControl render itself again on command. Or maybe you can replace the metadata on TabControl.ContentTemplate.
I have a XamGrid with two columns, Name and Type. Depending on Type, I want to have a different kind of column for Name, thus I'm using a TemplateColumn. In the data template I have a ContentControl with a default ContentTemplate and a DataTrigger that sets the ContentTemplate to a different column style if Type is a specific value.
I am setting alll four templates (ItemTemplate, EditorTemplate, AddNewRowItemTemplate, AddNewRowEditorTemplate) of the TemplateColumn to this data template.
ItemTemplate, AddNewRowItemTemplate and AddNewRowEditorTemplate work as intended, however EditorTemplate does not, see attached pictures:
Here is my code:
MainWindow.xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ig="http://schemas.infragistics.com/xaml"
Width="640" Height="480" >
<Window.Resources>
<DataTemplate x:Key="EditorTemplate">
<TextBox Width="64"/>
</DataTemplate>
<DataTemplate x:Key="BoolEditorTemplate">
<CheckBox/>
</DataTemplate>
<DataTemplate x:Key="DataTemplate">
<ContentControl Content="{Binding }">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource EditorTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Type}" Value="bool">
<Setter Property="ContentTemplate" Value="{StaticResource BoolEditorTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
</Window.Resources>
<ig:XamGrid ItemsSource="{Binding DataCollection, RelativeSource={RelativeSource AncestorType=Window}}"
AutoGenerateColumns="False">
<ig:XamGrid.EditingSettings>
<ig:EditingSettings AllowEditing="Row" />
</ig:XamGrid.EditingSettings>
<ig:XamGrid.AddNewRowSettings>
<ig:AddNewRowSettings AllowAddNewRow="Top" />
</ig:XamGrid.AddNewRowSettings>
<ig:XamGrid.Columns>
<ig:TemplateColumn Key="Name"
ItemTemplate="{StaticResource DataTemplate}"
AddNewRowItemTemplate="{StaticResource DataTemplate}"
EditorTemplate="{StaticResource DataTemplate}"
AddNewRowEditorTemplate="{StaticResource DataTemplate}"/>
<ig:TextColumn Key="Type"/>
</ig:XamGrid.Columns>
</ig:XamGrid>
</Window>
MainWindow.xaml.cs:
using System.Collections.ObjectModel;
namespace WpfApplication1
{
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
}
public ObservableCollection<Data> DataCollection { get; } = new ObservableCollection<Data>
{
new Data { Name = "Foo", Type = "bool" },
new Data { Name = "Bar", Type = "enum" }
};
}
public class Data
{
public string Name { get; set; }
public string Type { get; set; }
}
}
As explained here on the infragistics forum, for this use case not only is an EditorTemplate needed, but also an EditorStyle.
<Style x:Key="EditorStyle" TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource EditorTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Type}" Value="bool">
<Setter Property="ContentTemplate" Value="{StaticResource BoolEditorTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
<DataTemplate x:Key="DataTemplate">
<ContentControl Content="{Binding }"
Style="{StaticResource EditorStyle}" />>
</DataTemplate>
[...]
<ig:TemplateColumn Key="Name"
ItemTemplate="{StaticResource DataTemplate}"
AddNewRowItemTemplate="{StaticResource DataTemplate}"
EditorTemplate="{StaticResource DataTemplate}"
AddNewRowEditorTemplate="{StaticResource DataTemplate}"
EditorStyle="{StaticResource EditorStyle}" />
I want to present data in a ListView using a GridView. Depending on the amount of data that is shown I want to make the columheaders collapse or to be visible.
I tried to acomplish this thus:
<Style TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="Focusable" Value="False"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ShowCompact}" Value="True">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
But this not working. How can it be done?
This should work just fine. A minimal recreation worked without any problems for me:
View Model:
public class ListWindowViewModel : INotifyPropertyChanged
{
private bool _showCompact;
public bool ShowCompact
{
get { return _showCompact; }
set
{
if (value == _showCompact)
return;
_showCompact = value;
this.OnPropertyChanged("ShowCompact");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
View:
<Window x:Class="StackOverflow.ListWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:System;assembly=mscorlib"
xmlns:l="clr-namespace:StackOverflow">
<Window.DataContext>
<l:ListWindowViewModel />
</Window.DataContext>
<Window.Resources>
<Style TargetType="GridViewColumnHeader">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=ShowCompact}" Value="True">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<DockPanel LastChildFill="True">
<CheckBox DockPanel.Dock="Top"
Content="Show Compact"
IsChecked="{Binding Path=ShowCompact, Mode=TwoWay}" />
<ListView>
<ListView.ItemsSource>
<x:Array Type="s:String">
<s:String>Item 1</s:String>
<s:String>Item 2</s:String>
<s:String>Item 3</s:String>
</x:Array>
</ListView.ItemsSource>
<ListView.View>
<GridView>
<GridViewColumn Header="Text" DisplayMemberBinding="{Binding .}" />
<GridViewColumn Header="Length" DisplayMemberBinding="{Binding Length}" />
</GridView>
</ListView.View>
</ListView>
</DockPanel>
</Window>
Double-check that you are binding against a public property and raising the necessary change notification events. It might help if you post the relevant parts of your view model and Xaml.
Honestly not sure where I am going wrong here, but for whatever reason the foreground style seen below is not being applied to the datagrid. I'm at a loss for this one as I really don't know how I would go about debugging the xaml.
<DataGrid Name="dgProperties" Background="#1E918D8D" SelectionMode="Extended" SelectionUnit="FullRow" ItemsSource="{Binding CurFieldData}" AutoGenerateColumns="False" CanUserReorderColumns="False" CanUserSortColumns="True" IsReadOnly="True">
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}" >
<Setter Property="Foreground" Value="#3535bb" />
<!--<Style.Triggers>
<DataTrigger Binding="{Binding Path=DiffState}" Value="Different">
<Setter Property="Foreground" Value="#3535BB" />
</DataTrigger>
</Style.Triggers>-->
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="Property" FontSize="12" Binding="{Binding Name}" Width="2*" />
<DataGridTextColumn Header="Left Value" FontSize="12" Binding="{Binding LeftValue}" Width="4*" />
<DataGridTextColumn Header="Right Value" FontSize="12" Binding="{Binding RightValue}" Width="4*"/>
</DataGrid.Columns>
</DataGrid>
You can probably tell through the commented out trigger, but I originally planned on recoloring all entries in the grid that are marked as different (an enum in the code behind). However, that wasn't working for me so I wanted to try and see if the style was being set at all, regardless of the trigger.
Does anybody see or know why this style is not being applied?
It seems to be working just fine, I have added my test code below incase it helps
Xaml:
<Window x:Class="WpfApplication7.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" Name="UI">
<DataGrid Name="dgProperties" Background="#1E918D8D" SelectionMode="Extended" SelectionUnit="FullRow" ItemsSource="{Binding Items, ElementName=UI}" AutoGenerateColumns="False" CanUserReorderColumns="False" CanUserSortColumns="True" IsReadOnly="True">
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}" >
<Setter Property="Foreground" Value="Black" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=DiffState}" Value="Different">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="Property" FontSize="12" Binding="{Binding Name}" Width="2*" />
<DataGridTextColumn Header="Left Value" FontSize="12" Binding="{Binding LeftValue}" Width="4*" />
<DataGridTextColumn Header="Right Value" FontSize="12" Binding="{Binding RightValue}" Width="4*"/>
</DataGrid.Columns>
</DataGrid>
</Window>
Code:
public partial class MainWindow : Window
{
private ObservableCollection<GridItem> items = new ObservableCollection<GridItem>();
public MainWindow()
{
InitializeComponent();
for (int i = 0; i < 1000; i++)
{
Items.Add(new GridItem { Name = "StackOverflow" + i, LeftValue = i % 4, RightValue = i % 2 });
}
}
public ObservableCollection<GridItem> Items
{
get { return items; }
set { items = value; }
}
}
public class GridItem
{
public string Name { get; set; }
public int LeftValue { get; set; }
public int RightValue { get; set; }
public DiffState DiffState
{
get
{
if (LeftValue == RightValue)
{
return DiffState.Same;
}
return DiffState.Different;
}
}
}
public enum DiffState
{
Different,
Same
}
Result:
The styling of a DataGridRow can be confounded by settings that occur higher up the object graph, especially the Alternating Row Background. As shown in sa_ddam213's answer, the Xaml works when nothing else is declared. So purely as a diagnostic accelerator, add these four lines to the DataGrid declaration...
RowBackground="Transparent"
Background="Transparent"
AlternatingRowBackground="Transparent"
Foreground="Transparent"
And then inspect your foreground property and its trigger. You can then mutate those four properties on the DataGrid to identify the confounding influence (and then remove it).
I have a enum let's say
enum MyEnum
{
FirstImage,
SecondImage,
ThirdImage,
FourthImage
};
I have binded this Enum to my combobox in XAML.
While defining an combobox I have defined an ItemTemplate of combox to take Two UI element:
TextBlock that show the enum value (Description)
Image
I have done this much in XAML.
I am wondering where I can specify the Image corrosponding to each item of Enum value in a combobox? Is that possible through data trigger ?
I really appreciate if anyone have the XAML for this scenario.
Many Thanks in advance
You can use a DataTrigger, but would be more maintainable if you used a Converter. Here's a sample that uses a DataTrigger for a view of the image and text by itself, and then the same DataTrigger to display the image and text in ListBox and ComboBox, and finally, a ListBox and ComboBox that use a Converter to display the image and text:
XAML
<Window x:Class="WpfSandbox.EnumToImage.EnumToImage"
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:local="clr-namespace:WpfSandbox.EnumToImage"
Title="Enum To Image" SizeToContent="WidthAndHeight" >
<Window.DataContext>
<local:ImageViewModel x:Name="Model" />
</Window.DataContext>
<Window.Resources>
<ObjectDataProvider x:Key="EnumDataProvider"
MethodName="GetValues"
ObjectType="{x:Type System:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="local:Decade"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
<local:DecadeEnumImageConverter x:Key="ImageConverter" />
<ControlTemplate x:Key="ImageTemplate" >
<StackPanel Orientation="Horizontal">
<Image x:Name="MyImage" Width="64" Height="32" />
<TextBlock Text="{Binding}" VerticalAlignment="Center" />
</StackPanel>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding}" Value="Ninties" >
<DataTrigger.Setters>
<Setter TargetName="MyImage"
Property="Source"
Value="/EnumToImage/images/90s.jpg"/>
</DataTrigger.Setters>
</DataTrigger>
<DataTrigger Binding="{Binding}" Value="Eighties" >
<DataTrigger.Setters>
<Setter TargetName="MyImage"
Property="Source"
Value="/EnumToImage/images/80s.jpg"/>
</DataTrigger.Setters>
</DataTrigger>
<DataTrigger Binding="{Binding}" Value="Seventies" >
<DataTrigger.Setters>
<Setter TargetName="MyImage"
Property="Source"
Value="/EnumToImage/images/70s.jpg"/>
</DataTrigger.Setters>
</DataTrigger>
<DataTrigger Binding="{Binding}" Value="Sixties" >
<DataTrigger.Setters>
<Setter TargetName="MyImage"
Property="Source"
Value="/EnumToImage/images/60s.jpg"/>
</DataTrigger.Setters>
</DataTrigger>
<DataTrigger Binding="{Binding}" Value="Fifties" >
<DataTrigger.Setters>
<Setter TargetName="MyImage"
Property="Source"
Value="/EnumToImage/images/50s.jpg"/>
</DataTrigger.Setters>
</DataTrigger>
<DataTrigger Binding="{Binding}" Value="Forties" >
<DataTrigger.Setters>
<Setter TargetName="MyImage"
Property="Source"
Value="/EnumToImage/images/40s.jpg"/>
</DataTrigger.Setters>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<DataTemplate x:Key="ItemsTemplateWithConverter">
<StackPanel Orientation="Horizontal">
<Image Width="64" Height="32"
Source="{Binding Converter={StaticResource ImageConverter}}"/>
<TextBlock Text="{Binding}" VerticalAlignment="Center" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="ItemsTemplateWithDataTrigger">
<ContentControl Template="{StaticResource ImageTemplate}" />
</DataTemplate>
</Window.Resources>
<StackPanel>
<ContentControl Margin="10" MouseUp="OnImageMouseUp"
HorizontalAlignment="Center" Cursor="Hand"
DataContext="{Binding Path=ImageEnum}"
Template="{StaticResource ImageTemplate}" />
<StackPanel Orientation="Horizontal">
<StackPanel>
<ListView Margin="10"
ItemsSource="{Binding Source={StaticResource EnumDataProvider}}"
ItemTemplate="{StaticResource ItemsTemplateWithConverter}" />
<ComboBox Margin="10"
ItemsSource="{Binding Source={StaticResource EnumDataProvider}}"
ItemTemplate="{StaticResource ItemsTemplateWithConverter}" />
</StackPanel>
<StackPanel>
<ListView Margin="10"
ItemsSource="{Binding Source={StaticResource EnumDataProvider}}"
ItemTemplate="{StaticResource ItemsTemplateWithDataTrigger}" />
<ComboBox Margin="10"
ItemsSource="{Binding Source={StaticResource EnumDataProvider}}"
ItemTemplate="{StaticResource ItemsTemplateWithDataTrigger}" />
</StackPanel>
</StackPanel>
</StackPanel>
</Window>
Code Behind
using System;
using System.ComponentModel;
using System.Globalization;
using System.Windows;
using System.Windows.Input;
using System.Windows.Data;
namespace WpfSandbox.EnumToImage
{
/// <summary>
/// Interaction logic for EnumToImage.xaml
/// </summary>
public partial class EnumToImage : Window
{
public EnumToImage()
{
InitializeComponent();
}
private int i = 1;
private void OnImageMouseUp( object sender, MouseButtonEventArgs e )
{
i++;
Model.ImageEnum = ( Decade )i;
if( i == 6 )
i = 0;
}
}
public enum Decade
{
Ninties = 1,
Eighties = 2,
Seventies = 3,
Sixties = 4,
Fifties = 5,
Forties = 6,
};
public class ImageViewModel : INotifyPropertyChanged
{
private Decade _imageEnum;
public Decade ImageEnum
{
get { return _imageEnum; }
set
{
_imageEnum = value;
RaisePropertyChanged( "ImageEnum" );
}
}
public ImageViewModel()
{
ImageEnum = Decade.Ninties;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged( string propertyName )
{
var handler = PropertyChanged;
if( handler != null )
{
handler( this, new PropertyChangedEventArgs( propertyName ) );
}
}
}
public class DecadeEnumImageConverter : IValueConverter
{
public object Convert( object value, Type targetType, object parameter, CultureInfo culture )
{
var myEnum = ( Decade )Enum.Parse( typeof( Decade ), value.ToString() );
switch( myEnum )
{
case Decade.Ninties:
return "/EnumToImage/images/90s.jpg";
case Decade.Eighties:
return "/EnumToImage/images/80s.jpg";
case Decade.Seventies:
return "/EnumToImage/images/70s.jpg";
case Decade.Sixties:
return "/EnumToImage/images/60s.jpg";
case Decade.Fifties:
return "/EnumToImage/images/50s.jpg";
case Decade.Forties:
return "/EnumToImage/images/40s.jpg";
default:
throw new ArgumentOutOfRangeException();
}
}
public object ConvertBack( object value, Type targetType, object parameter, CultureInfo culture )
{
throw new NotImplementedException();
}
}
}