WPF ListView & Converter - wpf

I have a ListView with a GridViewColumn that has an image in it, it uses a converter to convert a bool to a ImageSource. It was working perfectly for months and now it suddenly just shows a red dot instead of my image.
Converter:
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if ((bool)value)
return new BitmapImage(new Uri(#"/Dionysus.Styling;component/Images/Actions-dialog-ok-apply-icon.png", UriKind.RelativeOrAbsolute));
else return
null;
}
Xaml:
<GridViewColumn Header="">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Image Source="{Binding Path=IsDelayedOrPreferred, Converter={StaticResource DelayConverter}, Mode=TwoWay}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
Result:
Has anyone seen this behaviour before or know what might have changed. The only recent changes that I made was upgrading to VS 2013 but all other ListViews with Converters
are still working as expected.
Style:
<Style TargetType="{x:Type ListView}">
<Setter Property="BorderBrush" Value="White"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="AlternationCount" Value="2"/>
<Setter Property="Background" Value="WhiteSmoke"/>
<EventSetter Event="Loaded" Handler="ListView_Loaded"/>
</Style>
<Style TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="Margin" Value="1,0"/>
</Style>
<Style TargetType="{x:Type ListViewItem}" x:Key="ListViewStyle">
<Style.Triggers>
<Trigger Property="ItemsControl.AlternationIndex" Value="0">
<Setter Property="Background" Value="WhiteSmoke"></Setter>
</Trigger>
<Trigger Property="ItemsControl.AlternationIndex" Value="1">
<Setter Property="Background" Value="White"></Setter>
</Trigger>
<DataTrigger Binding="{Binding IsFirst}" Value="True">
<Setter Property="Background" Value="LightGreen"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding IsLastUnpaid}" Value="True">
<Setter Property="Background" Value="LightSalmon"></Setter>
</DataTrigger>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="#FF41B1E1"></Setter>
</Trigger>
</Style.Triggers>
<Setter Property="Height" Value="20" />
<Setter Property="HorizontalAlignment" Value="Left"/>
</Style>
UPDATE:
I recently changed from a ListView to a DataGrid and then I see a little exclamation mark as if there is data validation errors on the DataGrid.
Any ideas?

Related

How can I highlight the cell which contains the error?

I have a DataGrid with four columns for which I have defined a style as well as a triggered style for the case the user enters an invalid value.
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="FontSize" Value="16"/>
<Setter Property="FontFamily" Value="ArialMT"/>
<Setter Property="Height" Value="24"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="ValidationErrorTemplate">
<Setter.Value>
<ControlTemplate>
<Grid>
<Ellipse Width="12" Height="12" Fill="Red" Stroke="Black" StrokeThickness="0.5"/>
<TextBlock FontWeight="Bold" Padding="4,0,0,0" Margin="0" VerticalAlignment="Top" Foreground="White" Text="!" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderBrush" Value="Red"/>
<Setter Property="IsEnabled" Value="True" />
<Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
This works well and the complete DataGridRow is marked as faulty because I used this in the XAML:
<DataGrid.RowValidationRules>
<local:CycleValidationRule ValidationStep="UpdatedValue" />
</DataGrid.RowValidationRules>
Now I want to highlight the DataGridCell with the invalid value explictely additionally (setting the background colour). Hence, I defined another style:
<Style x:Key="cycleErrStyle" TargetType="{x:Type TextBlock}">
<Setter Property="Background" Value="Magenta"/>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true" >
<Setter Property="Background" Value="Red" />
<Setter Property="Foreground" Value="White" />
</Trigger>
</Style.Triggers>
</Style>
but this doesn't work.
When I set <Trigger Property="Validation.HasError" Value="false" > to false, the style affects. It seems as if the Validation.HasError property has been reset after validation for the grid's row.
In the XAML I defined this:
<DataGridTextColumn x:Name="TagCycle" Header="Cycle" Binding="{Binding Mode=TwoWay, Path=RawTag.Cycle, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"
ElementStyle="{StaticResource ResourceKey=cycleErrStyle}" />
How can I highlight the invalid cell additionally to marking the row as faulty?
You could use a DataTrigger that binds to the Validation.HasError attached property of the parent DataGridRow:
<Style x:Key="cycleErrStyle" TargetType="{x:Type TextBlock}">
<Setter Property="Background" Value="Magenta"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=(Validation.HasError), RelativeSource={RelativeSource AncestorType=DataGridRow}}" Value="true" >
<Setter Property="Background" Value="Red" />
<Setter Property="Foreground" Value="White" />
</DataTrigger>
</Style.Triggers>
</Style>

WPF Combobox should only open on arrow

Is there a chance to change the wpf combobox that it only opens when I click on the arrow on the left side?
Usually you can click anywhere to open it. I dont want that.
Thanks
Usually you can click anywhere to open it. I dont want that.
Then you should create a custom template for the ToggleButton. Yan right-click on the a ComboBox element in design mode in Visual Studio or in Blend and choose Edit Template->Edit a copy.
This will copy the default template into your XAML markup and you can then edit it as per your requirements. Look for a Style with an x:Key of "ComboBoxToggleButton" and modify the ControlTemplate of this one.
I managed to get it working with the following code. Note that I am using Materialdesign and you have to change it a little if you are not using that.
Code in App.xaml
<Style x:Key="MaterialDesignComboBox2" TargetType="{x:Type ComboBox}">
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="{DynamicResource MaterialDesignTextBoxBorder}"/>
<Setter Property="Foreground" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type FrameworkElement}}, Path=(TextElement.Foreground)}"/>
<Setter Property="BorderThickness" Value="0 0 0 1"/>
<Setter Property="ItemContainerStyle" Value="{StaticResource MaterialDesignComboBoxItemSelectedCollapsedStyle}" />
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto" />
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" />
<Setter Property="Padding" Value="0 6 0 6" />
<Setter Property="VerticalContentAlignment" Value="Top" />
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="ScrollViewer.CanContentScroll" Value="true" />
<Setter Property="ScrollViewer.PanningMode" Value="Both" />
<Setter Property="Stylus.IsFlicksEnabled" Value="False" />
<Setter Property="Validation.ErrorTemplate" Value="{StaticResource MaterialDesignValidationErrorTemplate}"/>
<Setter Property="wpf:TextFieldAssist.TextBoxViewMargin" Value="1 0 1 0" />
<Setter Property="Template" Value="{StaticResource MaterialDesignFloatingHintComboBoxTemplate2}" />
<Style.Triggers>
<Trigger Property="IsKeyboardFocused" Value="true">
<Setter Property="BorderBrush" Value="{DynamicResource PrimaryHueMidBrush}" />
</Trigger>
<Trigger Property="IsKeyboardFocusWithin" Value="true">
<Setter Property="BorderBrush" Value="{DynamicResource PrimaryHueMidBrush}" />
</Trigger>
<Trigger Property="IsEditable" Value="true">
<Setter Property="IsTabStop" Value="false" />
<!-- designer prefers hard bool -->
<Setter Property="wpf:ComboBoxAssist.ShowSelectedItem" Value="{StaticResource TrueValue}" />
</Trigger>
<!-- designer prefers hard bool -->
<Trigger Property="wpf:ComboBoxAssist.ShowSelectedItem" Value="{StaticResource TrueValue}" >
<Setter Property="ItemContainerStyle" Value="{StaticResource MaterialDesignComboBoxItemStyle}" />
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="MaterialDesignFloatingHintComboBox2"
BasedOn="{StaticResource MaterialDesignComboBox2}"
TargetType="{x:Type ComboBox}">
<Setter Property="wpf:HintAssist.IsFloating" Value="True"/>
</Style>
Helperclass EditableComboBox
public static class EditableComboBox
{
public static int GetMaxLength(DependencyObject obj)
{
return (int)obj.GetValue(MaxLengthProperty);
}
public static void SetMaxLength(DependencyObject obj, int value)
{
obj.SetValue(MaxLengthProperty, value);
}
public static readonly DependencyProperty MaxLengthProperty = DependencyProperty.RegisterAttached("MaxLength", typeof(int), typeof(EditableComboBox), new UIPropertyMetadata(OnMaxLenghtChanged));
private static void OnMaxLenghtChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (!(obj is ComboBox comboBox)) return;
comboBox.Loaded +=
(s, e) =>
{
var textBox = comboBox.FindChild("PART_EditableTextBox", typeof(TextBox));
if (textBox == null) return;
textBox.SetValue(TextBox.MaxLengthProperty, args.NewValue);
};
}
}
And the combobox itself in the View
<ComboBox materialDesign:HintAssist.Hint="{lex:Loc ordernumber}" Style="{StaticResource MaterialDesignFloatingHintComboBox2}" Text="{Binding SalesOrderNumber}" Margin="0 0 40 0" FontSize="22" Width="250"
IsEditable="True" DisplayMemberPath="Identifier" SelectedItem="{Binding SelectedSalesOrder}"
ItemsSource="{Binding LastSalesOrders}" IsTextSearchEnabled="False" utility:EditableComboBox.MaxLength="10" x:Name="TbOrder" >
<ComboBox.InputBindings>
<KeyBinding Command="{Binding OpenSalesOrderOrCustomerCommand}" Key="Return" />
</ComboBox.InputBindings>
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{lex:Loc paste}" Command="{Binding PasteOrdernumberCommand}" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>

WPF One DataTrigger with multiple Values

I have a Slider and a TextBlock on my WPF window. The TextBlock needs to change the background, foreground and font size by the value of the slider, by rang.
I built a converter that receives the value of the slider and returns a 0, 1 or 3 for each group.
public class ValueByRange : IValueConverter
{
public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double.TryParse(value?.ToString(), out double dValue);
if (dValue > 80)
return 2;
else if (dValue > 50)
return 1;
return 0;
}
public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return false;
}
}
My TextBlock looks like this:
<TextBlock Width="30" Text="{Binding ElementName= theSlider, Path=Value}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="TextAlignment" Value="Center"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=theSlider, Path=Value, Converter={StaticResource ValueByRange}}" Value="1">
<Setter Property="Background" Value="Yellow"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="FontSize" Value="14"/>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=theSlider, Path=Value, Converter={StaticResource ValueByRange}}" Value="2">
<Setter Property="Background" Value="Red"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontSize" Value="16"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
It's working fine, but it doesn't feel like the best approach. The converter fires 2 time, each for every DataTrigger. I need more than this 3 ranges and that means the converter will fire more times.
It's also not helpful to build a converter for each property, for the same reason.
Is there a way to fire the converter only ones and then check the result (without using code beyond)?
I know this is not the correct syntax, but I mean something like this:
<DataTrigger Binding="{Binding ElementName=theSlider, Path=Value, Converter={StaticResource ValueByRange}}">
<DataTrigger.Value Value ="1">
<Setter Property="Background" Value="Yellow" />
<Setter Property="Foreground" Value="Black" />
<Setter Property="FontSize" Value="14" />
</DataTrigger.Value>
<DataTrigger.Value Value ="2">
<Setter Property="Background" Value="Red" />
<Setter Property="Foreground" Value="White" />
<Setter Property="FontSize" Value="16" />
</DataTrigger.Value>
</DataTrigger>
Well, as Clemens said, there's nothing wrong with having a converter called multiple times.
But if you really want to do it, here's an approach that works:
<Grid>
<Grid.Resources>
<local:ValueByRange x:Key="ValueByRange" />
<Style x:Key="TextBlockInLabelStyle" TargetType="TextBlock">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="TextAlignment" Value="Center"/>
<Style.Triggers>
<DataTrigger Binding="{Binding}" Value="1">
<Setter Property="Background" Value="Yellow"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="FontSize" Value="14"/>
</DataTrigger>
<DataTrigger Binding="{Binding}" Value="2">
<Setter Property="Background" Value="Red"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontSize" Value="16"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Slider Grid.Row="0"
x:Name="Slider1"
Interval="1"
Minimum="0"
Maximum="100" />
<Slider Grid.Row="1"
x:Name="Slider2"
Interval="1"
Minimum="0"
Maximum="100" />
<Label Grid.Row="2" Content="{Binding ElementName=Slider1, Path=Value, Converter={StaticResource ValueByRange}}">
<Label.Resources>
<Style TargetType="{x:Type TextBlock}" BasedOn="{StaticResource TextBlockInLabelStyle}" />
</Label.Resources>
</Label>
<Label Grid.Row="3" Content="{Binding ElementName=Slider2, Path=Value, Converter={StaticResource ValueByRange}}">
<Label.Resources>
<Style TargetType="{x:Type TextBlock}" BasedOn="{StaticResource TextBlockInLabelStyle}" />
</Label.Resources>
</Label>
</Grid>
I want to make an annotation to the answer above:
This makes use of a very confusing and badly documented feature, which is discussed here and confused the hell out of me when reading the answer.
To be clear: I did NOT understand why the TARGETTYPE here was TextBlock, when we clearly use a Label and the answer is discussed in the link above. It has to do with the fact that the valueconverter returns a DOUBLE, which gets treated with the TEXTBLOCK style while a LABEL style in its place would be ignored. I tested it the hard way...

One IValueConverter for all properties set in style

Is there a way to set a IValueConverter for a textbox when styling it?
The converter are used to hide zeros and showing placeholder values for empty values.
I would like to use the converter without knowing the name of the property of the bound object, so all bindings are using my converter, Can I do it?
<Style TargetType="{x:Type TextBox}">
<Setter Property="Height" Value="20"/>
<Setter Property="Margin" Value="0,0,0,6"/>
<Setter Property="Text">
<Setter.Value>
<Binding RelativeSource="{RelativeSource Self}" Converter="{textBoxTest:FormatIntAndDoubleValueConverter}"/>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsReadOnly" Value="True">
<Setter Property="Background" Value="{StaticResource DisabledBackgroundBrush}" />
</Trigger>
<Trigger Property="IsFocused" Value="True">
<Setter Property="BorderThickness" Value="2"></Setter>
</Trigger>
</Style.Triggers>
</Style>

DataTrigger not working on CheckBox

I have the following DataTrigger in a WPF form:
<CheckBox IsChecked="{Binding ConcentratorViewModel.Integrated}">
<CheckBox.Style>
<Style TargetType="{x:Type CheckBox}">
<Setter Property="Margin" Value="0,10,0,0"/>
<Setter Property="Width" Value="20"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<!--<Setter Property="IsEnabled" Value="False"/>-->
<!--<Setter Property="IsChecked" Value="False"/>-->
<Style.Triggers>
<DataTrigger Binding="{Binding ConcentratorViewModel.Manufacturer}" Value="ZIV">
<Setter Property="Background" Value="Red"/>
<Setter Property="IsChecked" Value="True"/>
</DataTrigger>
<DataTrigger Binding="{Binding ConcentratorViewModel.Manufacturer}" Value="Landis+Gyr">
<Setter Property="IsChecked" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
</CheckBox>
The CheckBox must be Checked or Unchecked depending on the selected manufacturer. I added a converter to see the value on the trigger and it's correct. I've also added the Background property and it changes correctly, but the IsChecked doesn't work.
Well you need to move the IsChecked Binding inside the Style. Setting it directly on the Checkbox gives it precedence and the Trigger's cannot change that value.
So something like:
<CheckBox>
<CheckBox.Style>
<Style TargetType="{x:Type CheckBox}">
<Setter Property="Margin"
Value="0,10,0,0" />
<Setter Property="Width"
Value="20" />
<Setter Property="VerticalAlignment"
Value="Center" />
<Setter Property="IsChecked"
Value="{Binding ConcentratorViewModel.Integrated}" />
<Style.Triggers>
<DataTrigger Binding="{Binding ConcentratorViewModel.Manufacturer}"
Value="ZIV">
<Setter Property="Background"
Value="Red" />
<Setter Property="IsChecked"
Value="True" />
</DataTrigger>
<DataTrigger Binding="{Binding ConcentratorViewModel.Manufacturer}"
Value="Landis+Gyr">
<Setter Property="IsChecked"
Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
</CheckBox>
Note:
Remember by doing this, your Binding only applies when the DataTrigger's do not evaluate to "True". Thus if your Manufacturer property is "ZIV" or "Landis+Gyr" then your Integrated property is not going to see any CheckBox updates even with a TwoWay binding as it just isn't being used.

Resources