Why this simple style doesn't apply? - c#

Why TextBlock remains black?
<Window x:Class="WpfApplication4.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">
<Window.Resources>
<Style TargetType="TextBlock" x:Key="style">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Tag}" Value="True">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<StackPanel>
<TextBlock Text="Test" Tag="True" Style="{StaticResource style}" />
</StackPanel>
</Window>
Update: Ok now I have another problem. The style does not react on property change:
<Window x:Class="WpfApplication4.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">
<Window.Resources>
<Style TargetType="TextBlock" x:Key="style">
<Style.Triggers>
<Trigger Property="Tag" Value="True">
<Setter Property="Foreground" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<StackPanel>
<TextBlock Text="{Binding Prop}" Tag="{Binding Prop}" Style="{StaticResource style}" x:Name="text" />
<Button Content="Test" Click="Button_Click" />
</StackPanel>
</Window>
Backing Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.ComponentModel;
namespace WpfApplication4
{
public partial class MainWindow : Window
{
private MyClass a = new MyClass();
public MainWindow()
{
InitializeComponent();
DataContext = a;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
a.Prop = true;
a.OnPropertyChanged("Prop");
}
}
public class MyClass : INotifyPropertyChanged
{
public bool Prop { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public virtual void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
TextBlock's text changes but color does not

Change it to Property Trigger
<Style TargetType="TextBlock" x:Key="style">
<Style.Triggers>
<Trigger Property="Tag" Value="True">
<Setter Property="Foreground" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
TemplatedParent works inside a ControlTemplate. Your Binding is incorrect. Thats why it doesn't work.
If you want to use DataTrigger for some reason then the correct Binding would be
<DataTrigger Binding="{Binding Tag,RelativeSource={RelativeSource Self}}" Value="True">

Simply change binding:
Binding="{Binding RelativeSource={RelativeSource Self}, Path=Tag}"

This primarily is happening because Tag property is of type object and not string. The solution given in below link might help you:
http://social.msdn.microsoft.com/forums/en-US/wpf/thread/d3424267-ed1f-4b30-90a1-5cca9843bd22
About Textblock.Tag property:
http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.tag.aspx

Related

How to bind property of dynamically created components of a user control to another component?

I have a user control which creates a set of radio buttons based on a list. The radio buttons are created using data template.
<UserControl.Resources>
<SelectableItem:SelectableItem x:Key="vm"></SelectableItem:SelectableItem>
<src:RadioButtonCheckedConverter x:Key="RadioButtonCheckedConverter" />
<CollectionViewSource
Source="{Binding Source={x:Static Application.Current}, Path=ItemDescription}"
x:Key="ListingDataView" />
<DataTemplate x:Key="GroupingHeaderTemplate">
<TextBlock Text="{Binding Path=Name}" Style="{StaticResource GroupHeaderStyle}"/>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ItemsControl Name="RadioGroup" AutomationProperties.Name="RadioGroup" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<RadioButton
GroupName="{Binding Path=ItemType}"
Content="{Binding Path=ItemDescription}"
FlowDirection="RightToLeft"
IsChecked="{Binding Path=IsSelected, Mode=TwoWay}" Style="{DynamicResource CustomRadioButton}" Margin="20,0" Checked="RadioButton_Checked" Tag="{Binding Path=ItemDescription, Mode=TwoWay}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
I use this in a window where I need to change the visibility of a component (stack panel) based on the selected radio button.
<uc:CommonRadioButtonGroup x:Name="SelectionButtonsGroup" ></uc:CommonRadioButtonGroup>
I am trying to change the visibility using style triggers.
<Style x:Key="spStyle" TargetType="StackPanel" >
<Style.Triggers>
<DataTrigger Binding="{Binding Source={x:Static Local:EngineModes.PresentMode}}" Value="Stop">
<Setter Property="StackPanel.Visibility" Value="Hidden" />
</DataTrigger>
<DataTrigger Binding="{Binding Source={x:Static Local:EngineModes.PresentMode}}" Value="Wait">
<Setter Property="StackPanel.Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding Source={x:Static Local:EngineModes.PresentMode}}" Value="Go">
<Setter Property="StackPanel.Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
I cannot figure out a way to implement the viewmodel for this one. I tried this one:
public class EngineModes : INotifyPropertyChanged
{
public static List<SelectableItem> Modes { get; set; } = new List<SelectableItem>();
public static string PresentMode { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public virtual void OnPropertyChanged(string propertyInfo)
{
App.Current.Dispatcher.BeginInvoke((Action)(() =>
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyInfo));
}
));
}
}
where "Modes" are the options of the radio button. But it simply does not work.
Ultimately, on selecting a mode using radio button, the visibility of the stack panel must be modified.
Please comment on the correctness of the code.
Edit:
Here is the ItemSource for the user control added in codebehind:
SelectionButtonsGroup.RadioGroup.ItemsSource = EngineModes.Modes;
this.DataContext = EngineModes.PresentMode;
I updated the style as
<Style x:Key="sprecStyle" TargetType="StackPanel">
<Style.Triggers>
<DataTrigger Binding="{Binding Source={StaticResource engineModes}, Path=PresentMode, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Value="Stop">
<Setter Property="StackPanel.Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding Source={StaticResource engineModes}, Path=PresentMode, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Value="Wait">
<Setter Property="StackPanel.Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding Source={StaticResource engineModes}, Path=PresentMode, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Value="Go">
<Setter Property="StackPanel.Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
And added a separate notified event for static property change:
public static event EventHandler<PropertyChangedEventArgs> StaticPropertyChanged
= delegate { };
public static void NotifyStaticPropertyChanged(string propertyName)
{
StaticPropertyChanged(null, new PropertyChangedEventArgs(propertyName));
}
and called it where the engine mode is updated.
EngineModes.NotifyStaticPropertyChanged("PresentMode");
And voila! It worked.

WPF Datagrid bind data list to a template column combobox

I've been searching for hours on how to bind data to combobox in a datagrid template column. I found some in the site but nothing seems to work. Here's what I've done so far:
public partial class CashReceipt : UserControl
{
private ObservableCollection<CashItem> itemsList;
private ObservableCollection<string> accounts;
public CashReceipt()
{
InitializeComponent();
itemsList = new ObservableCollection<CashItem>();
accounts = new ObservableCollection<string>()
{
"5710",
"6010",
"6510",
"7010"
};
for (int i = 0; i < 5; ++i)
{
CashItem item = new CashItem();
itemsList.Add(item);
}
clDatagrid.ItemsSource = itemsList;
}
}
public struct CashItem
{
public string account { get; set; }
public string description { get; set; }
public decimal amount { get; set; }
}
}
The XAML is
<UserControl x:Class="CashLedgerApp.CashReceipt"
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:CashLedgerApp"
mc:Ignorable="d"
MinHeight="320" HorizontalAlignment="Stretch">
<UserControl.Resources>
<CollectionViewSource x:Key="AccountsList" Source="{Binding Path=accounts}"/>
</UserControl.Resources>
<Border Margin="100,20" BorderBrush="gray" BorderThickness="0.5">
<Grid Background="White">
<DataGrid x:Name="clDatagrid" Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="8" Margin="10,0" VerticalAlignment="Stretch" MinHeight="30" AutoGenerateColumns="False" RowHeaderWidth="0" IsReadOnly="False" CanUserAddRows="True">
<DataGrid.Resources>
<Style BasedOn="{StaticResource {x:Type DataGridColumnHeader}}" TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="Height" Value="30"/>
<Setter Property="Background" Value="Black"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="BorderBrush" Value="White"/>
<Setter Property="BorderThickness" Value="0,0,1,0"/>
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
<!--<DataGridComboBoxColumn x:Name="clAccount" Header="Compte No" Width="1*" SelectedValuePath="{Binding account}" SelectedValueBinding="{Binding Path=account}"/>-->
<DataGridTemplateColumn Header="Compte No" Width="1*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Source={StaticResource AccountsList}}"
SelectedItem="{Binding account}"
DisplayMemberPath="account"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Description" Width="3*" Binding="{Binding description}"/>
<DataGridTextColumn Header="Montant" Width="1.5*" Binding="{Binding amount}">
<DataGridTextColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="Background" Value="Black"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="BorderThickness" Value="0"/>
</Style>
</DataGridTextColumn.HeaderStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Border>
</UserControl>
When I execute this program, the combobox in the template column is always empty. The data in the accounts list is just a sample to make things work. I'll use data from database in the final version of the code. But I need to make it work first.
Thanks.
UPDATE
I made changes as you suggested #ASh, here is the C# and XAML (I added some data to the itemList collection, as expected, those data are displayed in the grid but the ComboBox in the DataGridTemplateColumn are still empty):
public partial class CashReceipt : UserControl
{
public ObservableCollection<CashItem> itemsList;
public ObservableCollection<string> Accounts;
public CashReceipt()
{
InitializeComponent();
itemsList = new ObservableCollection<CashItem>();
Accounts = new ObservableCollection<string>()
{
"5710",
"6010",
"6510",
"7010"
};
for (int i = 0; i < 5; ++i)
{
CashItem item = new CashItem();
item.description = "Achats";
item.amount = 250000;
itemsList.Add(item);
}
clDatagrid.ItemsSource = itemsList;
}
}
public struct CashItem
{
public string account { get; set; }
public string description { get; set; }
public decimal amount { get; set; }
}
<UserControl x:Class="CashLedgerApp.CashReceipt"
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:CashLedgerApp"
mc:Ignorable="d"
MinHeight="320" HorizontalAlignment="Stretch">
<UserControl.Resources>
<CollectionViewSource x:Key="AccountsList" Source="{Binding Path=Accounts, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
</UserControl.Resources>
<Border Margin="100,20" BorderBrush="gray" BorderThickness="0.5">
<Grid Background="White">
<DataGrid x:Name="clDatagrid" Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="8" Margin="10,0" VerticalAlignment="Stretch" MinHeight="30" AutoGenerateColumns="False" RowHeaderWidth="0" IsReadOnly="False" CanUserAddRows="True">
<DataGrid.Resources>
<Style BasedOn="{StaticResource {x:Type DataGridColumnHeader}}" TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="Height" Value="30"/>
<Setter Property="Background" Value="Black"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="BorderBrush" Value="White"/>
<Setter Property="BorderThickness" Value="0,0,1,0"/>
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn Header="Compte No" Width="1*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Source={StaticResource AccountsList}}"
SelectedItem="{Binding account}"
DisplayMemberPath="account"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Description" Width="3*" Binding="{Binding description}"/>
<DataGridTextColumn Header="Montant" Width="1.5*" Binding="{Binding amount}">
<DataGridTextColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="Background" Value="Black"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="BorderThickness" Value="0"/>
</Style>
</DataGridTextColumn.HeaderStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Border>
</UserControl>
I might have missed something I think but can't figure out what.
Binding {Binding Path=accounts} will work if you make accounts a public property (currently it is a private field)
public ObservableCollection<string> accounts { get; private set; }
also naming conventions suggest it should become Accounts (and {Binding Path=Accounts})
additionally simple Binding searches properties in DataContext, but in your case Accounts is a property of UserControl itself, so it is necessary to change source of Binding:
<CollectionViewSource x:Key="AccountsList"
Source="{Binding Path=Accounts, RelativeSource={RelativeSource AncestorType=UserControl}}"/>

Change way of highlight selected rows in DataGrid with styled cells

I'm creating WPF App and I'm using DataGrid. The problem is after I styled DataGridCell to have margins and alignment in every cell, selection only highlights the background behind text, not all cells.
Something like this:
Code behind styling:
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Margin" Value="8,0,8,0"/>
</Style>
</DataGrid.CellStyle>
How to prevent it? There is some way to do it?\
Edit: I reproduce it on clean project without problems, all code of example:
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<DataGrid Name="grid" Margin="10">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Width="0.2*" Binding="{Binding first_name}"/>
<DataGridTextColumn Header="Last Name" Width="0.2*" Binding="{Binding last_name}"/>
<DataGridTextColumn Header="E-mail" Width="0.2*" Binding="{Binding email}"/>
<DataGridTextColumn Header="Phone number" Width="0.2*" Binding="{Binding phone}"/>
</DataGrid.Columns>
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Margin" Value="8,0,8,0"/>
</Style>
</DataGrid.CellStyle>
</DataGrid>
</Grid>
Class of that xaml:
using System.Windows;
namespace WpfApp1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private Client client = new Client();
public MainWindow()
{
InitializeComponent();
client = new Client()
{
first_name = "John",
last_name = "Connor",
email = "john_connor#mail.com",
phone = "123456789"
};
this.grid.DataContext = client;
this.grid.Items.Add(client);
}
}
public class Client
{
public string first_name { get; set; }
public string last_name { get; set; }
public string email { get; set; }
public string phone { get; set; }
}
}
It seems, that Margin makes problems. So try to get rid of Margin if you can.
If not, I'm afraid you have to override the default control template to change the DataGrid's behavior and have a clean solution. As work around you could change a color of the selected row, but if the DataGrid lost focus it will be ugly again.
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Margin" Value="8,0,8,0"/>
</Style>
</DataGrid.CellStyle>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
I would also use HorizontalContentAlignment and VerticalContentAlignment instead of HorizontalAlignment and VerticalAlignment.
E.g. you could also use DataGridTemplateColumn instead of DataGridTextColumn and put Margin to the control in DataTemplate:
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding first_name}" Margin="8,0,8,0"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!--Other columns-->
</DataGrid.Columns>

Item command cannot edit another item in the list

I have a sidebar (in a C# WPF program) that should display 4 "different" buttons (They are actually 2 different styles, which both have another style for the active state). The sidebar consists of an ItemsControl. I've now managed to create a list where the correct style is used based on an enum value (as shown below). Here's a small question: Can I do it this way, or should I rewrite it, and if so, how could something like this be built? Keywords or something that I have to look at are enough for me.
My real question now is: I have bound a command to every button, nothing complicated at first. The command now sets its own state to NormalActive for testing purposes. The 1st item in this list should be set from LiveActive to Live (so that you always see the currently selected item as you know it). And here's the problem: The button can set its own state, so when I click on button 3, the state of button 3 is set from Normal to NormalActive. But what doesn't happen is the change from LiveActive to Active from the 1st button. Even if I output the current state to the console before and after the change, it returns LiveActive for both. I also tried invoking the whole thing into the dispatcher if I'm not in the UI thread for some reason, it didn't work. So the button can set its own state, but not the one of another. But I don't get an error message or anything. Also the setter method of the property is called, it just doesn't change it. What could be the reason?
PluginListControl:
<Grid DataContext="{x:Static local:PluginListDesignModel.Instance}">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:PluginListItemControl />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
PluginListItemControl:
<UserControl.Resources>
<DataTemplate x:Key="PluginTile" DataType="{x:Type local:PluginListItemViewModel}">
<Button Style="{StaticResource PluginTile}" Content="{Binding DataContext.Name, RelativeSource={RelativeSource AncestorType=ContentControl}}" Command="{Binding DataContext.SetStateCommand, RelativeSource={RelativeSource AncestorType=ContentControl}}" />
</DataTemplate>
<DataTemplate x:Key="PluginActiveTile" DataType="{x:Type local:PluginListItemViewModel}">
<Button Style="{StaticResource PluginActiveTile}" Content="{Binding DataContext.Name, RelativeSource={RelativeSource AncestorType=ContentControl}}" Command="{Binding DataContext.SetStateCommand, RelativeSource={RelativeSource AncestorType=ContentControl}}" />
</DataTemplate>
<DataTemplate x:Key="PluginLiveTile" DataType="{x:Type local:PluginListItemViewModel}">
<Button Style="{StaticResource PluginLiveTile}" Content="{Binding DataContext.Name, RelativeSource={RelativeSource AncestorType=ContentControl}}" Command="{Binding DataContext.SetStateCommand, RelativeSource={RelativeSource AncestorType=ContentControl}}" />
</DataTemplate>
<DataTemplate x:Key="PluginActiveLiveTile" DataType="{x:Type local:PluginListItemViewModel}">
<Button Style="{StaticResource PluginActiveLiveTile}" Content="{Binding DataContext.Name, RelativeSource={RelativeSource AncestorType=ContentControl}}" Command="{Binding DataContext.SetStateCommand, RelativeSource={RelativeSource AncestorType=ContentControl}}" />
</DataTemplate>
</UserControl.Resources>
<ContentControl d:DataContext="{x:Static local:PluginListItemDesignModel.Instance}">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource PluginTile}" />
<Style.Triggers>
<DataTrigger Binding="{Binding State}" Value="0">
<Setter Property="ContentTemplate" Value="{StaticResource PluginTile}" />
</DataTrigger>
<DataTrigger Binding="{Binding State}" Value="1">
<Setter Property="ContentTemplate" Value="{StaticResource PluginActiveTile}" />
</DataTrigger>
<DataTrigger Binding="{Binding State}" Value="2">
<Setter Property="ContentTemplate" Value="{StaticResource PluginLiveTile}" />
</DataTrigger>
<DataTrigger Binding="{Binding State}" Value="3">
<Setter Property="ContentTemplate" Value="{StaticResource PluginActiveLiveTile}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
PluginListItemViewModel: (The ViewModel for each list item)
public class PluginListItemViewModel : BaseViewModel
{
public string Name { get; set; }
public PluginTileStates State { get; set; }
public ICommand SetStateCommand { get; set; }
#region Constructor
/// <summary>
/// Default constructor
/// </summary>
public PluginListItemViewModel()
{
SetStateCommand = new RelayCommand(() => SetState());
}
#endregion
private void SetState()
{
PluginListDesignModel.Instance.Items[0].State = PluginTileStates.Live;
State = PluginTileStates.NormalActive;
}
}
Steps to reproduce:
Create a new WPF project, .NET Framework 4.6.1 (Visual Studio 2017).
Replace the grid in MainWindow with the following:
<Grid DataContext="{x:Static local:ListViewModel.Instance}">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:ListItemControl />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
Add a new UserControl named ListItemControl and replace the grid with:
<UserControl.Resources>
<Style x:Key="Tile" TargetType="{x:Type Button}">
<Setter Property="Background" Value="Red" />
</Style>
<Style x:Key="ActiveTile" TargetType="{x:Type Button}">
<Setter Property="Background" Value="Green" />
</Style>
<DataTemplate x:Key="PluginTile" DataType="{x:Type local:ListItemViewModel}">
<Button Width="100" Height="60" Style="{StaticResource Tile}" Command="{Binding DataContext.SetStateCommand, RelativeSource={RelativeSource AncestorType=ContentControl}}" />
</DataTemplate>
<DataTemplate x:Key="PluginActiveTile" DataType="{x:Type local:ListItemViewModel}">
<Button Width="100" Height="60" Style="{StaticResource ActiveTile}" Command="{Binding DataContext.SetStateCommand, RelativeSource={RelativeSource AncestorType=ContentControl}}" />
</DataTemplate>
</UserControl.Resources>
<ContentControl>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource PluginTile}" />
<Style.Triggers>
<DataTrigger Binding="{Binding State}" Value="0">
<Setter Property="ContentTemplate" Value="{StaticResource PluginTile}" />
</DataTrigger>
<DataTrigger Binding="{Binding State}" Value="1">
<Setter Property="ContentTemplate" Value="{StaticResource PluginActiveTile}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
Add a new class called BaseViewModel and replace class with:
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };
public void OnPropertyChanged(string name)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
Add new class called ListItemViewModel and replace class with:
public enum TileStates
{
Normal = 0,
Active = 1
}
public class ListItemViewModel : BaseViewModel
{
public TileStates State { get; set; }
public ICommand SetStateCommand { get; set; }
public ListItemViewModel()
{
SetStateCommand = new RelayCommand(() =>
{
ListViewModel.Instance.Items[0].State = TileStates.Normal;
State = TileStates.Active;
});
}
}
Add new class called ListViewModel and replace class with:
public class ListViewModel : BaseViewModel
{
public static ListViewModel Instance => new ListViewModel();
public List<ListItemViewModel> Items { get; set; } = new List<ListItemViewModel>
{
new ListItemViewModel
{
State = TileStates.Active
},
new ListItemViewModel
{
State = TileStates.Normal
}
};
}
Add new class called RelayCommand and replace class with:
public class RelayCommand : ICommand
{
private Action mAction;
public event EventHandler CanExecuteChanged = (sender, e) => { };
public RelayCommand(Action action)
{
mAction = action;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
mAction();
}
}
Install the NuGet-Packages: "Fody v4.0.2" and "PropertyChanged.Fody v2.6.0" (You probably have to restart Visual Studio after installation
If you now press the bottom button, it should get green and the top one should switch to red.
ListViewModel.Instance returns a new instance of the ListViewModel class each time it's invoked. It should return the same instance:
public static ListViewModel Instance { get; } = new ListViewModel();

WPF DataTrigger setters changing Window Width and Height not having effect

I've extended Window to add some functionality, and part of this is the ability to specify a specific window size or allow it to size to the content. The codebehind looks like this, currently un-MVVMified.
public partial class DialogWindow : Window
{
public bool HasSize { get; set; }
public Size Size { get; set; }
}
The XAML then looks like this:
<Window ... Name="DialogWindowElement">
<Window.Style>
<Style TargetType="Window">
<Style.Triggers>
<DataTrigger Binding="{Binding HasSize, ElementName=DialogWindowElement}" Value="True">
<Setter Property="Width" Value="{Binding Size.Width, ElementName=DialogWindowElement}" />
<Setter Property="Height" Value="{Binding Size.Height, ElementName=DialogWindowElement}" />
</DataTrigger>
<DataTrigger Binding="{Binding HasSize, ElementName=DialogWindowElement}" Value="False">
<Setter Property="SizeToContent" Value="WidthAndHeight" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Style>
<ContentControl ...>
<!-- Content control using DataTemplates to determine content -->
</ContentControl>
</Window>
Resizing to content seems to work okay, but the specified width and height aren't applied. Any large content expands to all the size it needs instead of being constrained and then resizable later.
Snoop and other such tools imply the trigger is fired, but the setters don't seem to be having any effect.
Am I missing something here?
Edit: Added content control to the window to provide some more context
This works for me:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
public bool HasSize { get; set; } = true;
public Size Size { get; set; } = new Size(800, 800);
}
XAML:
<Window x:Class="WpfApplication1.Window21"
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"
mc:Ignorable="d"
Title="Window1"
Name="DialogWindowElement">
<Window.Style>
<Style TargetType="Window">
<Style.Triggers>
<DataTrigger Binding="{Binding HasSize, ElementName=DialogWindowElement}" Value="True">
<Setter Property="Width" Value="{Binding Size.Width, ElementName=DialogWindowElement}" />
<Setter Property="Height" Value="{Binding Size.Height, ElementName=DialogWindowElement}" />
</DataTrigger>
<DataTrigger Binding="{Binding HasSize, ElementName=DialogWindowElement}" Value="False">
<Setter Property="SizeToContent" Value="WidthAndHeight" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Style>
<TextBlock Text="Test..." FontSize="40" FontWeight="Bold" />
</Window>
Make sure that you don't set the Width and Height properties of the window in your XAML because local values take take precedence over values set by style setters.

Categories