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>
Related
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);
}
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");
}
}
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>
Sure Im missing something simple but I have overridden a checkbox as a user control so I can use different images as its bullet decorators. Seems to work ok but doesnt respond to any click or checked events. I need it to display the bound popup upon the click evnt but not change the bullet decorator images.
Also my binding seems to be broken as when the click even fires (once it works) it should present a popup with the bound data. WarningList is my data to bind and when debugging it shows its populated correctly, just not bound correctly I believe
<UserControl x:Class="Dev.App.Views.Controls.StatusResultCheckBox"
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:converters="clr-namespace:Dev.App.Views.Converters"
x:Name="Root"
d:DesignHeight="50"
d:DesignWidth="50"
mc:Ignorable="d">
<UserControl.Resources>
<converters:WarningListFilterByOperationTypeConverter x:Key="WarningListFilterByOperationTypeConverter" />
<DataTemplate x:Key="StatusResultNone">
<Viewbox Width="20" Height="20">
<Grid/>
</Viewbox>
</DataTemplate>
<DataTemplate x:Key="StatusResultWarning">
<BulletDecorator Background="Transparent">
<BulletDecorator.Bullet>
<Viewbox Width="20" Height="20">
<Grid>
<Path Data="F1M874.094,289.369L854.3,254.63C854.028,254.151 853.515,253.856 852.958,253.856 852.403,253.856 851.89,254.151 851.617,254.63L831.824,289.369C831.555,289.84 831.559,290.416 831.835,290.883 832.111,291.348 832.618,291.634 833.165,291.634L872.752,291.634C873.299,291.634 873.805,291.348 874.081,290.883 874.357,290.416 874.361,289.84 874.094,289.369z"
Stretch="Uniform" Fill="#FFFCCE00" Width="20" Height="20" Margin="0,0,0,0" RenderTransformOrigin="0.5,0.5" StrokeThickness="0.5">
</Path>
<Path Data="M855.653,287.189L850.264,287.189 850.264,282.745 855.653,282.745 855.653,287.189z M855.653,279.41L850.264,279.41 850.264,266.077 855.653,266.077 855.653,279.41z"
Stretch="Uniform" Fill="Black" Width="3" Height="10" Margin="0.5,3,0,0" RenderTransformOrigin="0.5,0.5" StrokeThickness="0.5">
</Path>
</Grid>
</Viewbox>
</BulletDecorator.Bullet>
<Popup Name="WarningMessagePopup"
Width="{Binding ElementName=ListBox, Path=ActualWidth}"
Height="200"
HorizontalAlignment="Center"
HorizontalOffset="50"
PlacementTarget="{Binding RelativeSource={RelativeSource TemplatedParent}}"
IsOpen="{Binding Path=IsChecked}"
StaysOpen="False"
VerticalOffset="10">
<TextBox VerticalScrollBarVisibility="Auto">
<TextBox.Text>
<MultiBinding Converter="{StaticResource WarningListFilterByOperationTypeConverter}" Mode="OneWay">
<Binding Path="OperationType" />
<Binding Path="DataContext.WarningList" RelativeSource="{RelativeSource AncestorType=ListBox}" />
</MultiBinding>
</TextBox.Text>
</TextBox>
</Popup>
</BulletDecorator>
</DataTemplate>
<ControlTemplate x:Key="CheckBoxTemplate" TargetType="ContentControl">
<ContentControl Name="Content" />
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding StatusResultCheckMode, ElementName=Root}" Value="None">
<Setter TargetName="Content" Property="ContentTemplate" Value="{StaticResource StatusResultNone}" />
</DataTrigger>
<DataTrigger Binding="{Binding StatusResultCheckMode, ElementName=Root}" Value="Warning">
<Setter TargetName="Content" Property="ContentTemplate" Value="{StaticResource StatusResultWarning}" />
<Setter Property="Cursor" Value="Hand" />
</DataTrigger>
<DataTrigger Binding="{Binding HasStatus, ElementName=Root}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<Style x:Key="StatusResultVisibilityStyle" TargetType="CheckBox">
<Setter Property="OverridesDefaultStyle" Value="true" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<BulletDecorator Background="Transparent">
<BulletDecorator.Bullet>
<Viewbox Width="20" Height="20">
<Grid />
</Viewbox>
</BulletDecorator.Bullet>
</BulletDecorator>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsInStatusNone.Value}" Value="False" />
<Condition Binding="{Binding IsInStatusWarning.Value}" Value="True" />
</MultiDataTrigger.Conditions>
<Setter Property="Visibility" Value="Visible" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<CheckBox HorizontalAlignment="Center"
VerticalAlignment="Center"
IsChecked="True"
Style="{StaticResource StatusResultVisibilityStyle}"/>
<ContentControl Grid.Column="0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Width="20" Height="20"
Template="{StaticResource CheckBoxTemplate}" />
</Grid>
</UserControl>
Called from my main page as follows. (nested inside a Listbox)
<controls:StatusResultCheckBox Grid.Column="3"
IsInStatusWarning="{Binding Path=IsInStatusWarning.Value}"
IsInStatusNone="{Binding Path=IsInStatusNone.Value}"
HasStatus="{Binding Path=HasStatus.Value}"
IsRunning="{Binding RelativeSource={RelativeSource AncestorType=ListBox}, Path=DataContext.IsRunning.Value}"/>
The code behind the user control...
using System.Windows;
using System.Windows.Controls;
namespace Dev.App.Views.Controls
{
/// <summary>
/// Interaction logic for StatusResultCheckBox.xaml
/// </summary>
public partial class StatusResultCheckBox : UserControl
{
public static readonly DependencyProperty StatusResultCheckModeProperty =
DependencyProperty.Register("StatusResultCheckMode", typeof(StatusResultCheckMode), typeof(StatusResultCheckBox), new PropertyMetadata(StatusResultCheckMode.None));
public static readonly DependencyProperty IsRunningProperty =
DependencyProperty.Register("IsRunning", typeof(bool), typeof(StatusResultCheckBox), new PropertyMetadata(false, IsRunningPropertyChanged));
public static readonly DependencyProperty HasStatusProperty =
DependencyProperty.Register("HasStatus", typeof(bool), typeof(StatusResultCheckBox), new PropertyMetadata(false, IsRunningPropertyChanged));
public static readonly DependencyProperty IsInStatusWarningProperty =
DependencyProperty.Register("IsInStatusWarning", typeof(bool), typeof(StatusResultCheckBox), new PropertyMetadata(false, IsRunningPropertyChanged));
public static readonly DependencyProperty IsInStatusNoneProperty =
DependencyProperty.Register("IsInStatusNone", typeof(bool), typeof(StatusResultCheckBox), new PropertyMetadata(false, IsRunningPropertyChanged));
private static void IsRunningPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var statusResultCheckBox = d as StatusResultCheckBox;
if (statusResultCheckBox != null)
{
if (statusResultCheckBox.IsRunning)
{
if (statusResultCheckBox.IsInStatusWarning)
{
statusResultCheckBox.StatusResultCheckMode = StatusResultCheckMode.Warning;
statusResultCheckBox.HasStatus = true;
}
else
{
statusResultCheckBox.StatusResultCheckMode = StatusResultCheckMode.None;
statusResultCheckBox.HasStatus = false;
}
}
}
}
public StatusResultCheckBox()
{
InitializeComponent();
}
public StatusResultCheckMode StatusResultCheckMode
{
get { return (StatusResultCheckMode)GetValue(StatusResultCheckModeProperty); }
set { SetValue(StatusResultCheckModeProperty, value); }
}
public bool IsRunning
{
get { return (bool)GetValue(IsRunningProperty); }
set { SetValue(IsRunningProperty, value); }
}
public bool HasStatus
{
get { return (bool)GetValue(HasStatusProperty); }
set { SetValue(HasStatusProperty, value); }
}
public bool IsInStatusWarning
{
get { return (bool)GetValue(IsInStatusWarningProperty); }
set { SetValue(IsInStatusWarningProperty, value); }
}
public bool IsInStatusNone
{
get { return (bool)GetValue(IsInStatusNoneProperty); }
set { SetValue(IsInStatusNoneProperty, value); }
}
}
public enum StatusResultCheckMode
{
None,
Warning
}
}
If you want to do something like this, it probably will be easier if you just apply a style to a existing checkbox.
You can get the default style from here and change what you need.
Ended up refactoring all this code and simplifying. Basically styled a togglebutton.
<ToggleButton Grid.Column="3" Style="{StaticResource WarningStyle}"/>
And in the resource file...
<Style x:Key="WarningStyle" TargetType="ToggleButton">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="Cursor" Value="Hand" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Visibility" Value="Collapsed" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Grid>
<Path Data="F1M874.094,289.369L854.3,254.63C854.028,254.151 853.515,253.856 852.958,253.856 852.403,253.856 851.89,254.151 851.617,254.63L831.824,289.369C831.555,289.84 831.559,290.416 831.835,290.883 832.111,291.348 832.618,291.634 833.165,291.634L872.752,291.634C873.299,291.634 873.805,291.348 874.081,290.883 874.357,290.416 874.361,289.84 874.094,289.369z"
Stretch="Uniform" Fill="#FFFCCE00" Width="20" Height="20" Margin="0,0,0,0" RenderTransformOrigin="0.5,0.5" StrokeThickness="0.5">
</Path>
<Path Data="M855.653,287.189L850.264,287.189 850.264,282.745 855.653,282.745 855.653,287.189z M855.653,279.41L850.264,279.41 850.264,266.077 855.653,266.077 855.653,279.41z"
Stretch="Uniform" Fill="Black" Width="3" Height="10" Margin="0.5,3,0,0" RenderTransformOrigin="0.5,0.5" StrokeThickness="0.5">
</Path>
<Popup Name="WarningMessagePopup"
Width="425"
Height="150"
HorizontalAlignment="Center"
HorizontalOffset="22"
PlacementTarget="{Binding RelativeSource={RelativeSource TemplatedParent}}"
IsOpen="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsChecked}"
StaysOpen="False"
VerticalOffset="7">
<ListBox ItemsSource="{Binding Path=WarningList}">
<ListBox.Style>
<Style TargetType="{x:Type ListBox}">
<Style.Triggers>
<Trigger Property="IsVisible" Value="True">
<Setter Property="Background" Value="#F8FCFF"/>
</Trigger>
</Style.Triggers>
</Style>
</ListBox.Style>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource WarningListFilterByOperationTypeConverter}" Mode="OneWay">
<Binding Path="Count" />
<Binding Path="Message" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Popup>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding IsInStatusWarning.Value}" Value="True" >
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
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>