WPF DataTrigger setters changing Window Width and Height not having effect - c#

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.

Related

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>

WPF, setting background color with trigger

I have a customcontrol which render a textbox. I've also a style that set the color of the background based on some conditions as follows:
<Style x:Key="ArtParamStyle" TargetType="av:DC_Base">
<Setter Property="Background" Value="{StaticResource EditableAreaBrush}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Info.Upd.IsAutoCalc}" Value="True">
<Setter Property="Background" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=Forced}" Value="True">
<Setter Property="Background" Value="LightGreen" />
</DataTrigger>
</Style.Triggers>
</Style>
Initially as the value of my textbox is autocalculated, the background is correctly red. If I also set the Forced as true (by ticking a chebckbox) I've a weird result, the border of textbox is lightgreen but background not.
It seems to be a strange color, a combination of red and lightgreen. As test, if I set the "IsAutoCalc" color as Transparent, the trigger works correctly. How can I solve this?
your code seems to be correct. But I provide you my sample:
XAML:
<Window.Resources>
<Style x:Key="ArtParamStyle" TargetType="TextBox">
<Setter Property="Background" Value="Yellow" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Bool1}" Value="True"/>
<Condition Binding="{Binding Bool2}" Value="False"/>
</MultiDataTrigger.Conditions>
<Setter Property="Background" Value="Red" />
<Setter Property="BorderBrush" Value="Red" />
</MultiDataTrigger>
<DataTrigger Binding="{Binding Bool2}" Value="True">
<Setter Property="Background" Value="LightGreen" />
<Setter Property="BorderBrush" Value="LightGreen" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<StackPanel>
<TextBox Style="{StaticResource ArtParamStyle}" Height="50" Margin="4"/>
<CheckBox IsChecked="{Binding Bool1}"/>
<CheckBox IsChecked="{Binding Bool2}"/>
</StackPanel>
</Grid>
In this case I used Multidatatrigger to set red background when Bool2(your forced) is not checked.
MainWindow.cs:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChaged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
private bool bool1;
public bool Bool1
{
get { return bool1; }
set { bool1 = value; RaisePropertyChaged("Bool1"); }
}
private bool bool2;
public bool Bool2
{
get { return bool2; }
set { bool2 = value; RaisePropertyChaged("Bool2"); }
}
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
}
Probably your problem is related to your custom control.

DataBinding does not work. System.Windows.Data tracing does not give any useful info

<ItemsControl ItemsSource="{Binding Tariffs}" Margin="6">
<ItemsControl.ItemTemplate>
<DataTemplate>
<custControls:RoundButton Name="TariffButton" Margin="3"
Content="{Binding TariffName}" Style="{DynamicResource TariffButton}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cal:ActionMessage MethodName="PickUpTariff">
<cal:Parameter Value="{Binding Path=Content,
RelativeSource={RelativeSource AncestorType={x:Type custControls:RoundButton}}}" />
</cal:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</custControls:RoundButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Relative source binding works just fine. Content="{Binding TariffName}" works just fint to.
But here is what contained in the Style TariffButton.
<Style x:Key="TariffButton" TargetType="customControls:RoundButton" BasedOn="{StaticResource {x:Type Button}}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsChosen, Mode=OneWay}" Value="True">
<Setter Property="Background" Value="Green"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsChosen, Mode=OneWay}" Value="False">
<Setter Property="Background" Value="Blue"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
And it does not work! More accurately, it works just once. So the data trigger is invoked only once.
Here is the ViewModel part:
public class DocumentChoiceViewModel:PropertyChangedBase {
public ObservableCollection<Tariff> Tariffs { get; private set; }
public DocumentChoiceViewModel() {
Tariffs = new ObservableCollection<Tariff> {
new Tariff {IsChosen = true, TariffName = "ПАССАЖИРСКИЙ"},
new Tariff {IsChosen = false, TariffName = "ЭКСПРЕСС"},
new Tariff {IsChosen = false, TariffName = "КОМФОРТ"},
new Tariff {IsChosen = false, TariffName = "ДОП.СК.М-НОГИНСК"}
};
}
public void PickUpTariff(string tariffName) {
if (!IsTariffPlanExists(tariffName))
throw new InvalidOperationException(
"Unexpectable state. User should not be able to choose tariff plan which is not present in Tariffs list");
}
}
public class Tariff:PropertyChangedBase {
private bool isChosen;
public bool IsChosen {
get { return isChosen; }
set {
isChosen = value;
NotifyOfPropertyChange(()=>IsChosen);
}
}
public string TariffName { get; set; }
}
So first if you have some problems debugging bindings - use a converter as an additional layer where you will see all changes.
And second - set binding mode to be TwoWay.
Style Triggers only work with Dependency Properties defined in the TargetType.
Why don't you just make an IsChosenToBackgroundConverter and write this
<RoundButton Background="{Binding IsChosen, Converter={StaticResource IsChosenToBackgroundConverter}"/>
I don't know if this will fix your problem, but you don't need to add a Trigger for both True and False conditions:
<Style x:Key="TariffButton" TargetType="customControls:RoundButton"
BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Background" Value="Blue" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsChosen}" Value="True">
<Setter Property="Background" Value="Green" />
</DataTrigger>
</Style.Triggers>
</Style>

Why this simple style doesn't apply?

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

Categories