DataAnnotations OrderBy Length Errors - c#

This method returns property errors (dataannotations). What I want to do is to return those errors sorted by length (ValidationMessage.Length). The list is sorted, but the problem is in the view, it shows them disordered. Can you please help me? Thank you
public void Validate(object currentInstance, string propertyName)
{
if (_validationErrors.ContainsKey(propertyName))
{
_validationErrors.Remove(propertyName);
}
var propertyInfo = currentInstance.GetType().GetProperty(propertyName);
var propertyValue = propertyInfo.GetValue(currentInstance, null);
var validationAttributes = propertyInfo.GetCustomAttributes(true).OfType<ValidationAttribute>();
var validationErrors =
validationAttributes
.Select(
x => new CustomErrorType
{
ValidationMessage = x.FormatErrorMessage(string.Empty),
Severity = x.IsValid(propertyValue) ? Severity.SUCCESS : Severity.ERROR
}
).ToList().OrderBy(x => x.ValidationMessage.Length);;
if (validationErrors.Any(x => x.Severity == Severity.ERROR))
{
_validationErrors.Add(propertyName, validationErrors);
}
}
.xaml
<Validation.ErrorTemplate>
<ControlTemplate>
<StackPanel>
<AdornedElementPlaceholder x:Name="textBox" />
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ErrorContent.ValidationMessage}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Foreground"
Value="Red" />
<Style.Triggers>
<DataTrigger Binding="{Binding ErrorContent.Severity}"
Value="{x:Static customEnums:Severity.WARNING}">
<Setter Property="Foreground"
Value="Orange" />
</DataTrigger>
<DataTrigger Binding="{Binding ErrorContent.Severity}"
Value="{x:Static customEnums:Severity.SUCCESS}">
<Setter Property="Foreground"
Value="DarkGreen" />
<Setter Property="FontWeight"
Value="Bold" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ControlTemplate>
</Validation.ErrorTemplate>

You could bind to a CollectionViewSource that sorts the validation messages:
<Validation.ErrorTemplate>
<ControlTemplate>
<StackPanel xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase">
<StackPanel.Resources>
<CollectionViewSource x:Key="cvs" Source="{Binding}">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="ErrorContent" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
</StackPanel.Resources>
<AdornedElementPlaceholder x:Name="textBox" />
<ItemsControl ItemsSource="{Binding Source={StaticResource cvs}}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ErrorContent}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="Red" />
<Style.Triggers>
<DataTrigger Binding="{Binding ErrorContent.Severity}"
Value="{x:Static customEnums:Severity.WARNING}">
<Setter Property="Foreground"
Value="Orange" />
</DataTrigger>
<DataTrigger Binding="{Binding ErrorContent.Severity}"
Value="{x:Static customEnums:Severity.SUCCESS}">
<Setter Property="Foreground"
Value="DarkGreen" />
<Setter Property="FontWeight"
Value="Bold" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ControlTemplate>
</Validation.ErrorTemplate>

Related

C#/WPF Main Window freezes when loading data into datagrid

guys I am currently working on a existing WPF Application build with Prism build and .NET 4.6. I have added a new Page which contains a few User Controls. In one of the User Controls I need to load a csv with a few hundred rows(always under thousand) into a datagrid.
Every time I load the data into the datagrid the whole Application freezes for a few seconds. I have tried to do the loading async with the dispatcher but the application freezes while loading. I have also tried to do it with a task but then I always got an exception("only UI Thread is allowed to change the observable collection")
Below my current async implementation which did not work, any help or idea is highly appreciated.
Thank you
Product XAML
<UserControl x:Class="Module.UserControls.Products"
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:Module.UserControls"
xmlns:mvvm="http://prismlibrary.com/"
mvvm:ViewModelLocator.AutoWireViewModel="true"
mc:Ignorable="d"
d:DesignHeight="650" d:DesignWidth="800" Background="{DynamicResource Module.Background}" MaxHeight="1500">
<UserControl.Resources>
<Style TargetType="DataGridCell">
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
<Style TargetType="TextBox">
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="FontSize" Value="16" />
<Setter Property="Foreground" Value="{DynamicResource Module.Textbox.Foreground}" />
<Setter Property="Margin" Value="5,5,5,5" />
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}" />
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="CheckBox">
<Setter Property="Margin" Value="5,5,5,5" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
<Style TargetType="Button">
<Setter Property="FontSize" Value="16" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="Background" Value="{DynamicResource Module.Button.Background}" />
<Setter Property="Foreground" Value="White" />
</Style>
<Style TargetType="ComboBox">
<Setter Property="Margin" Value="5,5,5,5" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="IsSynchronizedWithCurrentItem" Value="True" />
</Style>
</UserControl.Resources>
<Grid>
<Grid Margin="0,20,0,0" Background="{DynamicResource Module.Block.Background}">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Grid.Row="0">
<TextBlock Text="Products" Style="{DynamicResource Module.H2}" />
</Label>
<ScrollViewer Grid.Column="0" Grid.Row="1" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" Margin="0,10,0,10" OverridesDefaultStyle="True" >
<ScrollViewer.Resources>
<Style TargetType="{x:Type ScrollBar}">
<Setter Property="Background" Value="LightGray"/>
</Style>
</ScrollViewer.Resources>
<DockPanel HorizontalAlignment="Stretch">
<DataGrid AutoGenerateColumns = "False" IsReadOnly="False" ItemsSource="{Binding ProductCollection, UpdateSourceTrigger=PropertyChanged, IsAsync=True, Mode=TwoWay}" HorizontalAlignment="Center"
Width="Auto" HorizontalContentAlignment="Center">
<DataGrid.Resources>
<Style TargetType="DataGridCell">
<Setter Property="Foreground" Value="Black" />
<Setter Property="FontSize" Value="16" />
<Setter Property="BorderBrush" Value="White" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="{x:Null}" />
<Setter Property="BorderBrush" Value="{x:Null}" />
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Header = "Position" Binding="{Binding Path=InProductPos, UpdateSourceTrigger=PropertyChanged}" Width="Auto" MinWidth="120" />
<DataGridTextColumn Header = "Layout Position" Binding="{Binding Path = InProductLayout, UpdateSourceTrigger=PropertyChanged}" Width="Auto" MinWidth="120"/>
<DataGridTextColumn Header = "Name" Binding="{Binding Path=InProductDescription, UpdateSourceTrigger=PropertyChanged}" Width="Auto" MinWidth="300"/>
<DataGridTextColumn Header = "Product Quantity" Binding="{Binding Path=InProductPieces, UpdateSourceTrigger=PropertyChanged}" Width="Auto" MinWidth="120"/>
<DataGridTextColumn Header = "Class" Binding="{Binding Path=InProductClass, UpdateSourceTrigger=PropertyChanged}" Width="Auto" MinWidth="120"/>
<DataGridTextColumn Header = "Product Part ID" Binding="{Binding Path=InProductPartID, UpdateSourceTrigger=PropertyChanged}" Width="Auto" MinWidth="120"/>
<DataGridTextColumn Header = "Number" Binding="{Binding Path=InProductNumber, UpdateSourceTrigger=PropertyChanged}" Width="Auto" MinWidth="120"/>
<DataGridTextColumn Header = "Part list Name" Binding="{Binding Path=InProductPartlistName, UpdateSourceTrigger=PropertyChanged}" Width="Auto" MinWidth="300"/>
</DataGrid.Columns>
</DataGrid>
</DockPanel>
</ScrollViewer>
</Grid>
</Grid>
</UserControl>
Button XAML
<UserControl x:Class="Module.UserControls.ButtonRow"
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:resx="clr-namespace:Module.Properties"
xmlns:local="clr-namespace:Module.UserControls"
xmlns:mvvm="http://prismlibrary.com/"
mvvm:ViewModelLocator.AutoWireViewModel="true"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis" />
<Style TargetType="DataGridCell">
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
<Style TargetType="TextBox">
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="FontSize" Value="16" />
<Setter Property="Foreground" Value="{DynamicResource Module.Textbox.Foreground}" />
<Setter Property="Margin" Value="5,5,5,5" />
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}" />
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="StyleButtonWhite" TargetType="Button">
<Setter Property="Foreground" Value="White" />
</Style>
<Style x:Key="StyleButtonWhiteWhite" TargetType="Button">
<Setter Property="Foreground" Value="White" />
<Setter Property="Background" Value="{x:Null}" />
</Style>
<Style TargetType="Button">
<Setter Property="FontSize" Value="16" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="Background" Value="{DynamicResource Module.Button.Background}" />
<Setter Property="Foreground" Value="White" />
</Style>
<Style TargetType="ComboBox">
<Setter Property="Margin" Value="5,5,5,5" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
</UserControl.Resources>
<Grid>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,10,0,10">
<Button
Margin="12,0,12,0"
Command="{Binding LoadProductsCommand}"
Width="101" Height="40"
Style="{StaticResource StyleButtonWhite}"
Background="{DynamicResource Module.Button.Background}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Import Products" Foreground="White" />
</StackPanel>
</Button>
</StackPanel>
</Grid>
</UserControl>
Part of the ViewModel
public DelegateCommand LoadProductsCommand => new DelegateCommand(LoadProducts, () => true);
private ObservableCollection<Product> _productCollection;
public ObservableCollection<Products> ProductCollection
{
get => _productCollection;
set
{
_productCollection = value;
OnPropertyChanged();
}
}
public async void LoadProducts()
{
if (Dispatcher.CurrentDispatcher.CheckAccess())
{
await Dispatcher.CurrentDispatcher.InvokeAsync(() =>
{
ProductCollection.AddRange(FileImport.ImportProducts());
});
}
}
Class of the collection
public string InProductPos
{
get => _inProductPos;
set
{
_inProductPos = value;
OnPropertyChanged();
}
}
public string InProductLayout
{
get => _inProductLayout;
set
{
_inProductLayout = value;
OnPropertyChanged();
}
}
public string InProductDescription
{
get => _inProductDescription;
set
{
_inProductDescription = value;
OnPropertyChanged();
}
}
public int InProductPieces
{
get => _inProductPieces;
set
{
_inProductPieces = value;
OnPropertyChanged();
}
}
public int InProductClass
{
get => _inProductClass;
set
{
_inProductClass = value;
OnPropertyChanged();
}
}
public int InProductPartID
{
get => _inProductPartID;
set
{
_inProductPartID = value;
OnPropertyChanged();
}
}
public string InProductNumber
{
get => _inProductNumber;
set
{
_inProductNumber = value;
OnPropertyChanged();
}
}
public string InProductPartlistName
{
get => _inProductPartlistName;
set
{
_inProductPartlistName = value;
OnPropertyChanged();
}
}
You may try something like shown below. It loads the Product collection in a background thread, and only adds them to the ObservableCollection in the UI thread.
public async Task LoadProducts()
{
var products = await Task.Run(() => FileImport.ImportProducts());
ProductCollection.AddRange(products);
}

WPF: Change the CornerRadius for only the last item in a ListBox

I've created a ListBox with round corners. I've also added a bottom border to all ListBoxItems except for the last one.
The ListBoxItems, however, have normal, square corners, so when hovering over or selecting the first or last items, you can see an overlap between the round ListBox corner and the square ListBoxItem corners.
I can't set CornerRadius the same way I set BorderThickness - I think that's because CornerRadius is a property of the Border property(?).
I can force ALL ListBoxItems to have all round corners which fixes the overlap, but then ALL ListBoxItems have round underlines & selections- which I'd rather not have. I only want those round corners on the bottom of the last item (and eventually top of the first item)
I would like to use a similar sort of trigger for setting CornerRadius that I do for setting BrushThickness.
Is there a way to set the corner radius of just the last item in a ListBox? (and eventually the first item)
In my test, I'm using the MaterialDesignTheme package from NuGet. Because that's non-standard, I'll add all my code here (also note: I'm new to WPF, so feel free to critique anything that looks off):
App.xaml:
<Application . . .
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<materialDesign:BundledTheme BaseTheme="Light" PrimaryColor="DeepPurple" SecondaryColor="Lime" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
MainWindow.xaml: (Note, if you uncomment the commented section, it styles all ListBoxItems like I would want only the last ListBoxItem styled)
<Window . . .
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
TextElement.Foreground="{DynamicResource MaterialDesignBody}"
TextElement.FontWeight="Regular"
TextElement.FontSize="13"
TextOptions.TextFormattingMode="Ideal"
TextOptions.TextRenderingMode="Auto"
Background="{DynamicResource MaterialDesignPaper}"
FontFamily="{DynamicResource MaterialDesignFont}">
<Window.Resources>
<local:IsLastItemInContainerConverter x:Key="IsLastItemInContainerConverter" />
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="200*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="1" Margin="5">
<ListBox x:Name="GameListBox"
BorderBrush="{DynamicResource MaterialDesignDivider}"
BorderThickness="1">
<ListBox.Resources>
<Style TargetType="Border">
<Setter Property="CornerRadius" Value="10"/>
</Style>
</ListBox.Resources>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource MaterialDesignListBoxItem}">
<!--<Style.Resources>
<Style TargetType="Border">
<Setter Property="CornerRadius" Value="0 0 10 10"/>
</Style>
</Style.Resources>-->
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self},
Converter={StaticResource IsLastItemInContainerConverter}}" Value="False">
<Setter Property="BorderThickness" Value="0 0 0 1" />
<Setter Property="BorderBrush" Value="{DynamicResource MaterialDesignDivider}"/>
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self},
Converter={StaticResource IsLastItemInContainerConverter}}" Value="True">
<Setter Property="BorderThickness" Value="0" />
<Setter Property="BorderBrush" Value="Transparent"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
<ListBoxItem>
<TextBlock> Plain
</TextBlock>
</ListBoxItem>
<ListBoxItem>
<TextBlock> Old
</TextBlock>
</ListBoxItem>
<ListBoxItem>
<TextBlock> ListBox
</TextBlock>
</ListBoxItem>
<ListBoxItem>
<TextBlock> Full of junk
</TextBlock>
</ListBoxItem>
</ListBox>
</StackPanel>
</Grid>
</Window>
...and in MainWindow.xaml.cs, I have defined the converter to find the last item:
public class IsLastItemInContainerConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
DependencyObject item = (DependencyObject)value;
ItemsControl ic = ItemsControl.ItemsControlFromItemContainer(item);
return ic.ItemContainerGenerator.IndexFromContainer(item)
== ic.Items.Count - 1;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
First of all, please understand that the MaterialDesignTheme package was not used in my code.
Items.cs
Instead of using Converter, I added a model class.
public class Items
{
public string Name { get; set; }
public bool IsFirst { get; set; }
public bool IsLast { get; set; }
}
App.xaml
I defined styles of ListBoxItem, ListBox like below.
<Style TargetType="{x:Type ListBoxItem}" x:Key="listboxitem">
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border x:Name="border"
Background="White"
BorderBrush="#AAAAAA"
BorderThickness="1 1 1 0"
CornerRadius="0">
<TextBlock Text="{Binding Name}" Foreground="Black" FontSize="13" FontWeight="Normal"
Margin="10" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding IsFirst}" Value="True">
<Setter TargetName="border" Property="CornerRadius" Value="10 10 0 0"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsLast}" Value="True">
<Setter TargetName="border" Property="CornerRadius" Value="0 0 10 10"/>
<Setter TargetName="border" Property="BorderThickness" Value="1 1 1 1"/>
</DataTrigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="border" Property="Background" Value="#666666"/>
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="border" Property="Background" Value="#666666"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type ListBox}" x:Key="listbox">
<Setter Property="Width" Value="200"/>
<Setter Property="Height" Value="200"/>
<Setter Property="ItemContainerStyle" Value="{StaticResource listboxitem}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBox}">
<Border Background="Transparent"
BorderBrush="#AAAAAA"
BorderThickness="0 0 0 0"
CornerRadius="10">
<ItemsPresenter/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
MainWindow.xaml
<ListBox x:Name="lbx" Style="{StaticResource listbox}"/>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
lbx.ItemsSource = GetItems();
}
private List<Items> GetItems()
{
List<Items> source = new List<Items>();
source.Add(new Items { Name = "Plain", IsFirst = true });
source.Add(new Items { Name = "Old" });
source.Add(new Items { Name = "ListBox" });
source.Add(new Items { Name = "Full of junk", IsLast = true }); ;
return source;
}
}
It will be shown like this..

How bind validation to RadioButton WPF

I have some problem with Radiobutton binding to show validation.
I create two RadioButtons
<StackPanel Grid.Column="2" Grid.Row="1" Orientation="Horizontal" VerticalAlignment="Top" Margin="0,13,0,0">
<RadioButton IsChecked="{Binding EndUser, Mode=TwoWay}" Content="End User" />
<RadioButton IsChecked="{Binding AppDeveloper, Mode=TwoWay}" Margin="15,0,0,0" Content="App Developer"/>
</StackPanel>
for my logic I should take 1 name from 2.
I write the logic
[Required]
public string Role
{
get => role;
set
{
Set(ref role, value);
RaisePropertyChanged("EndUser");
RaisePropertyChanged("AppDeveloper");
}
}
public bool EndUser
{
get => Role.Contains("EndUser");
set => Role = "EndUser";
}
public bool AppDeveloper
{
get => Role.Contains("AppDeveloper");
set => Role = "AppDeveloper";
}
the problem is how show [Required] in a form, and if i choose 1 of them, the validation will be true (validation work right if it's need i show the validation code)
I find this Validation Rule for Radio buttons wpf ,but this example doesn't work for me, broke all logic(doesn't send to me anything) and doesn't mark my field.
how write the
<Binding.ValidationRules>
<DataErrorValidationRule />
</Binding.ValidationRules>
for radiobutton fields like in TextBox and mark it red?
my button which disable if the fields is not valid
<Button x:Name="SignInButton" Command="{Binding SignInCommand}" Grid.Row="4" Grid.Column="1" Grid.ColumnSpan="2" Content="Sign In" >
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="MidnightBlue"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontSize" Value="16"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Width" Value="100"/>
<Setter Property="Height" Value="40"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}" CornerRadius="10">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Height="23" Margin="24,5,24,4"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=(Validation.HasError), ElementName=Password}" Value="False"/>
<Condition Binding="{Binding Path=(Validation.HasError), ElementName=Login}" Value="False"/>
<Condition Binding="{Binding Path=(Validation.HasError), ElementName=Role}" Value="False"/>
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="True"/>
</MultiDataTrigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#FF280895"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
in MultiDataTrigger i write the rule, if Validation.HasError for the field the button is disable
You could for example put a Border around the RadioButtons and use a Style with a DataTrigger that binds to the Role property:
<Border Grid.Column="2" Grid.Row="1" BorderBrush="Red" Margin="0,13,0,0">
<StackPanel Orientation="Horizontal" VerticalAlignment="Top">
<RadioButton IsChecked="{Binding EndUser, Mode=TwoWay}" Content="End User" />
<RadioButton IsChecked="{Binding AppDeveloper, Mode=TwoWay}" Margin="15,0,0,0" Content="App Developer"/>
</StackPanel>
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding Role}" Value="{x:Null}">
<Setter Property="BorderThickness" Value="1" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
You should then make the setter of the Role private and make sure to raise the PropertyChanged event for the involved properties:
private string role;
[Required]
public string Role
{
get => role;
private set
{
role = value;
RaisePropertyChanged("Role");
}
}
public bool EndUser
{
get => Role == "EndUser";
set
{
Role = "EndUser";
RaisePropertyChanged("EndUser");
RaisePropertyChanged("AppDeveloper");
}
}
public bool AppDeveloper
{
get => Role == "AppDeveloper";
set
{
Role = "AppDeveloper";
RaisePropertyChanged("AppDeveloper");
RaisePropertyChanged("EndUser");
}
}

WPF DataTemplate.Triggers don't evaluate

Trying a simple DataTemplate implementation that doesn't work for some reason. It seems like the Bindings inside the conditions are never evaluated, even on the initial load. Any input is appreciated.
<DataTemplate x:Key="ReadinessCellTemplate">
<StackPanel Orientation="Horizontal">
<Grid>
<Grid.Style>
<Style TargetType="Grid">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding Readiness}" Value="{x:Static db:ReadinessState.DEVELOPMENT}">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<Ellipse Width="14" Height="14" HorizontalAlignment="Center" VerticalAlignment="Center" Fill="#FF1B468A" />
<TextBlock TextAlignment="Center" Text="D" Foreground="White" />
</Grid>
<Path x:Name="PART_ShapePath" Height="14" Width="14" Fill="#FF1B468A">
<Path.ToolTip>
<ToolTip x:Name="PART_StatusToolTip" />
</Path.ToolTip>
</Path>
</StackPanel>
<DataTemplate.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding LastModifiedTimestamp, Converter={StaticResource IsNullConverter}, diag:PresentationTraceSources.TraceLevel=High}" Value="True" />
<Condition Binding="{Binding LastFailedBuildTimestamp, Converter={StaticResource IsNullConverter}}" Value="True" />
</MultiDataTrigger.Conditions>
<Setter TargetName="PART_ShapePath" Property="Visibility" Value="Collapsed" />
</MultiDataTrigger>
<DataTrigger Binding="{Binding LastFailedBuildTimestamp, Converter={StaticResource IsNullConverter}}" Value="False">
<Setter TargetName="PART_ShapePath" Property="Visibility" Value="Visible" />
<Setter TargetName="PART_ShapePath" Property="Data" Value="M9.0 0.0L0.0 16.0L18.0 16.0L9.00004001084 0.0ZM9.90797917744 14.0L8.0 14.0L8.0 12.0L10.0 12.0L10.0 14.053956628ZM9.43709923716 11.0L8.48917922657 11.0L8.0 6.87502426276L8.0 4.0L10.0 4.0L10.0 6.87502426276L9.43709923716 11.3799755923Z" />
<Setter TargetName="PART_StatusToolTip" Property="Content">
<Setter.Value>
<StackPanel>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding LastFailedBuildTimestamp, TargetNullValue=UNSPECIFIED, StringFormat={}When: {0}}" />
<TextBlock Text="{Binding LastBuildError, TargetNullValue=UNSPECIFIED, StringFormat={}Why: {0}}" />
</StackPanel>
</StackPanel>
</Setter.Value>
</Setter>
</DataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding LastModifiedTimestamp, Converter={StaticResource IsNullConverter}}" Value="False" />
<Condition Binding="{Binding LastFailedBuildTimestamp, Converter={StaticResource IsNullConverter}}" Value="True" />
</MultiDataTrigger.Conditions>
<Setter TargetName="PART_ShapePath" Property="Visibility" Value="Visible" />
<Setter TargetName="PART_ShapePath" Property="Data" Value="M 0,11 0,14 3.0056497,14 11.706214,5.220339 8.7005652,2.2146893 0,11 0,11 Z M 14,2.9265537 C 14.316384,2.6101695 14.316384,2.1355932 14,1.819209 L 12.180791,0 C 11.864407,-0.31638417 11.38983,-0.31638417 11.073446,0 L 9.6497174,1.4237288 12.655366,4.4293786 14,3 14,3 Z" />
<Setter TargetName="PART_StatusToolTip" Property="Content">
<Setter.Value>
<StackPanel>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding LastModifiedTimestamp, TargetNullValue=UNSPECIFIED, StringFormat={}When: {0}}" />
<TextBlock Text="{Binding LastModifiedBy, TargetNullValue=UNKNOWN, StringFormat={}Modified by: {0}}" />
</StackPanel>
</StackPanel>
</Setter.Value>
</Setter>
</MultiDataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
EDIT: The Datacontext is set to the object with the following properties' definitions:
public ReadinessState Readiness
{
get { return _payload.Readiness; }
set
{
bool t = _payload.Readiness != value;
if (t)
{
_payload.Readiness = value;
OnPropertyChanged("Readiness");
}
}
}
public DateTimeOffset? LastModifiedTimestamp
{
get
{
return _payload?.LastModifiedTimestamp;
}
set
{
if (_payload != null && _payload.LastModifiedTimestamp != value)
{
LastModifiedBy = LockingSession?.Username;
_payload.LastModifiedTimestamp = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("LastModifiedTimestamp"));
}
}
}
public DateTimeOffset? LastFailedBuildTimestamp
{
get
{
return _payload?.LastFailedBuildTimestamp;
}
set
{
if (_payload != null && _payload.LastFailedBuildTimestamp != value)
{
_payload.LastFailedBuildTimestamp = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("LastFailedBuildTimestamp"));
}
}
}
public string LastModifiedBy
{
get
{
return _payload?.LastModifiedBy;
}
private set
{
if (_payload != null && _payload.LastModifiedBy != value)
{
_payload.LastModifiedBy = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("LastModifiedBy"));
}
}
}
public string LastBuildError
{
get
{
return _payload?.LastBuildError;
}
set
{
if (_payload != null && _payload.LastBuildError != value)
{
_payload.LastBuildError = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("LastBuildError"));
}
}
}
The binding to Readiness works fine, but LastModifiedTimestamp isn't. IsNullConverter never gets called.
I ended up taking the easy out and put the triggers in the style of the element I tried to manipulate:
<Path x:Name="PART_ShapePath" Height="14" Width="16" Fill="#FF1B468A">
<Path.Style>
<Style TargetType="Path">
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding LastModifiedTimestamp, Converter={StaticResource IsNullConverter}}" Value="True" />
<Condition Binding="{Binding LastFailedBuildTimestamp, Converter={StaticResource IsNullConverter}}" Value="True" />
</MultiDataTrigger.Conditions>
<Setter Property="Visibility" Value="Collapsed" />
</MultiDataTrigger>
<DataTrigger Binding="{Binding LastFailedBuildTimestamp, Converter={StaticResource IsNullConverter}}" Value="False">
<Setter Property="Visibility" Value="Visible" />
<Setter Property="Data" Value="M9.0 0.0L0.0 16.0L18.0 16.0L9.00004001084 0.0ZM9.90797917744 14.0L8.0 14.0L8.0 12.0L10.0 12.0L10.0 14.053956628ZM9.43709923716 11.0L8.48917922657 11.0L8.0 6.87502426276L8.0 4.0L10.0 4.0L10.0 6.87502426276L9.43709923716 11.3799755923Z" />
<Setter Property="ToolTip">
<Setter.Value>
<StackPanel>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding LastFailedBuildTimestamp, TargetNullValue=UNSPECIFIED, StringFormat={}When: {0}}" />
<TextBlock Text="{Binding LastBuildError, TargetNullValue=UNSPECIFIED, StringFormat={}Why: {0}}" />
</StackPanel>
</StackPanel>
</Setter.Value>
</Setter>
</DataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding LastModifiedTimestamp, Converter={StaticResource IsNullConverter}}" Value="False" />
<Condition Binding="{Binding LastFailedBuildTimestamp, Converter={StaticResource IsNullConverter}}" Value="True" />
</MultiDataTrigger.Conditions>
<Setter Property="Visibility" Value="Visible" />
<Setter Property="Data" Value="M 0,11 0,14 3.0056497,14 11.706214,5.220339 8.7005652,2.2146893 0,11 0,11 Z M 14,2.9265537 C 14.316384,2.6101695 14.316384,2.1355932 14,1.819209 L 12.180791,0 C 11.864407,-0.31638417 11.38983,-0.31638417 11.073446,0 L 9.6497174,1.4237288 12.655366,4.4293786 14,3 14,3 Z" />
<Setter Property="ToolTip">
<Setter.Value>
<StackPanel>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding LastModifiedTimestamp, TargetNullValue=UNSPECIFIED, StringFormat={}When: {0}}" />
<TextBlock Text="{Binding LastModifiedBy, TargetNullValue=UNKNOWN, StringFormat={}Modified by: {0}}" />
</StackPanel>
</StackPanel>
</Setter.Value>
</Setter>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Path.Style>
</Path>

Cannot change the style of the selected item in a ListBox

I´m trying to change the focused/selected item of a ListBox. My application is based on this article.
At the moment I´m trying to set the ListBoxItem style via data templates:
<DataTemplate x:Key="ItemTemplate">
<TextBlock Text="{Binding}"
Foreground="Black"
FontFamily="Segoe UI"
FontSize="22"
HorizontalAlignment="Left"
Padding="15,10,0,0"
/>
</DataTemplate>
<DataTemplate x:Key="SelectedTemplate">
<TextBlock Text="{Binding}"
Foreground="Red"
FontFamily="Segoe UI"
FontSize="30"
HorizontalAlignment="Left"
Padding="15,10,0,0"
/>
</DataTemplate>
My idea was to switch between those templates using a trigger:
<Style TargetType="{x:Type ListBoxItem}" x:Key="ContainerStyle">
<Setter Property="ContentTemplate" Value="{StaticResource ItemTemplate}" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="ContentTemplate" Value="{StaticResource SelectedTemplate}" />
</Trigger>
</Style.Triggers>
</Style>
The ListBox looks like this:
<ListBox x:Name="valuesItemsCtrl"
BorderThickness="0"
ItemContainerStyle="{StaticResource ContainerStyle}"
Background="Transparent"
Tag="{Binding }">
<ListBox.AlternationCount>
<Binding>
<Binding.Path>Values.Count</Binding.Path>
</Binding>
</ListBox.AlternationCount>
<ListBox.ItemsSource>
<Binding>
<Binding.Path>Values</Binding.Path>
</Binding>
</ListBox.ItemsSource>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
At the end I add the template to another ListBox:
<ListBox x:Name="tumblersCtrl"
BorderThickness="0"
Background="Transparent"
ItemsSource="{Binding Tumblers, ElementName=thisCtrl}"
ItemTemplate="{StaticResource TumblerTemplate}">
</ListBox>
Thanks for any help or hint!
Use ItemTemplate and data triggers:
<ListBox ItemsSource="{Binding}">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"
Foreground="Black"
FontFamily="Segoe UI"
FontSize="22"
HorizontalAlignment="Left"
Padding="15,10,0,0"
x:Name="tbName"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter TargetName="tbName" Property="Foreground" Value="Red"/>
<Setter TargetName="tbName" Property="FontSize" Value="30"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
where bound data is a collection of:
public class ViewModel : ViewModelBase
{
public String Name
{
get { return name; }
set
{
if (name != value)
{
name = value;
OnPropertyChanged("Name");
}
}
}
private String name;
public Boolean IsSelected
{
get { return isSelected; }
set
{
if (isSelected != value)
{
isSelected = value;
OnPropertyChanged("IsSelected");
}
}
}
private Boolean isSelected;
}
Window code-behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new[]
{
new ViewModel { Name = "John", IsSelected = true },
new ViewModel { Name = "Mary" },
new ViewModel { Name = "Pater" },
new ViewModel { Name = "Jane" },
new ViewModel { Name = "James" },
};
}
}
If you want to change the templates use DataTemplateSelector.
Dismiss your ContainerStyle and instead set the ListBox.ItemsTemplateSelector to your custom datatemplateselector as static resource.
You can find a detailed example in this link.
EDIT :
According to your comment you don't need DataTemplate just set these two properties in the Trigger:
<Style TargetType="{x:Type ListBoxItem}" x:Key="ContainerStyle">
<Setter Property="Foreground" Value="Black" />
<Setter Property="FontSize" Value="22" />
<Setter Property="FontFamily" Value="Segoe UI" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="Padding" Value="15,10,0,0" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Foreground" Value="Red" />
<Setter Property="FontSize" Value="30" />
</Trigger>
</Style.Triggers>
</Style>

Categories