How to avoid error icon from WPF validation overlapping other elements - c#

I'm working on a WPF app and want to implement validation.
For displaying error messages etc. i'm using a style for the TextBox:
<Style TargetType="{x:Type TextBox}">
<Setter Property="Height"
Value="25"/>
<Setter Property="VerticalAlignment"
Value="Top"/>
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel LastChildFill="true">
<Border Background="Red"
DockPanel.Dock="right"
Margin="5,0,0,0"
Width="20"
Height="20"
CornerRadius="10"
ToolTip="{Binding ElementName=customAdorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">
<TextBlock Text="!"
VerticalAlignment="Center"
HorizontalAlignment="Center"
FontWeight="Bold"
Foreground="White">
</TextBlock>
</Border>
<AdornedElementPlaceholder Name="customAdorner"
VerticalAlignment="Center" >
<Border BorderBrush="Red"
BorderThickness="1" />
</AdornedElementPlaceholder>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<!--Additional style trigger for changing the background color of the textbox-->
<Style.Triggers>
<Trigger Property="Validation.HasError"
Value="true">
<Setter Property="Background"
Value="LightPink"/>
</Trigger>
</Style.Triggers>
</Style>
When an error in one of my textboxes occurs, the textbox gets a red border and to the right a red dot with a white "!" appears.
Now the problem is that the red dot to the right is overlapping the neighbour element.
Is there a way to avoid that kind of thing?
You can download an example from here:
WPF validation example
Then choose the project "Validation_ValidationRule" as startproject.
Thanks in advance!

I have changed your Window.Resources
<Window.Resources>
<Style x:Key="myErrorTemplate" TargetType="Control">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel LastChildFill="True">
<Ellipse DockPanel.Dock="Right"
ToolTip="{Binding ElementName=myTextbox,
Path=AdornedElement.(Validation.Errors)[0].ErrorContent}"
Width="15" Height="15"
Margin="-25,0,0,0"
StrokeThickness="1" Fill="Red" >
<Ellipse.Stroke>
<LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
<GradientStop Color="#FFFA0404" Offset="0"/>
<GradientStop Color="#FFC9C7C7" Offset="1"/>
</LinearGradientBrush>
</Ellipse.Stroke>
</Ellipse>
<TextBlock DockPanel.Dock="Right"
ToolTip="{Binding ElementName=myControl,
Path=AdornedElement.(Validation.Errors)[0].ErrorContent}"
Foreground="White"
FontSize="11pt"
Margin="-15,5,0,0" FontWeight="Bold">!
</TextBlock>
<Border BorderBrush="Red" BorderThickness="1">
<AdornedElementPlaceholder Name="myControl"/>
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<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="TextBox" BasedOn="{StaticResource myErrorTemplate}" />
<Style TargetType="CheckBox" BasedOn="{StaticResource myErrorTemplate}" />
<Style TargetType="ComboBox" BasedOn="{StaticResource myErrorTemplate}" />
</Window.Resources>
Try to adapt this for your needs

Related

C# WPF ComboBox with rounded corners IsMouseOver won't work

I found this code on doing rounded corners on combobox, I have modifyed it a little, but I have two Issues:
1) The mouseover on the combobox (textbox + togglebutton) won't
work. (I want the standard bahavior with blue background on mouseover, put red just to see if something happend)
2) when I click on the togglebutton I get the popup, but how can I make the textbox clickable to get the popup?
This is what I get:
This is what I want when mouseover (but with rounded corner)
Here are the code:
<Style x:Key="BorderStyle">
<Setter Property="Control.BorderBrush" Value="#A0A1A2" />
<Setter Property="Control.BorderThickness" Value="1" />
<Setter Property="Control.Background" Value="Transparent" />
<Setter Property="Control.Foreground" Value="#101010" />
<Setter Property="Control.FontFamily" Value="Arial" />
<Setter Property="Control.FontWeight" Value="Normal" />
<Setter Property="Control.FontStretch" Value="Normal" />
<Setter Property="Control.FontStyle" Value="Normal" />
</Style>
<Style x:Key="ComboBoxTextBoxStyle" TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Background" Value="Red" />
</Trigger>
</Style.Triggers>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Grid>
<Border CornerRadius="2,0,0,2"
BorderThickness="1,1,0,1"
Background="{TemplateBinding Background}"
BorderBrush="#A0A1A2">
<ScrollViewer x:Name="PART_ContentHost"/>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ComboBoxButtonStyle" TargetType="{x:Type ToggleButton}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Background" Value="Red" />
</Trigger>
</Style.Triggers>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Background="#EAEAEA"
x:Name="border"
CornerRadius="0,2,2,0"
BorderThickness="0,1,1,1"
BorderBrush="#A0A1A2">
<ContentPresenter />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="RoundComboBox" TargetType="{x:Type ComboBox}" BasedOn="{StaticResource BorderStyle}">
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="FontSize" Value="14px"/>
<Setter Property="IsReadOnly" Value="True"/>
<Setter Property="Height" Value="25"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ComboBox}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition MaxWidth="18"/>
</Grid.ColumnDefinitions>
<TextBox Name="PART_EditableTextBox"
Padding="0,0,0,0"
IsHitTestVisible="False"
Height="{TemplateBinding Height}"
BorderBrush="#A0A1A2"
Background="#EAEAEA"
Style="{StaticResource ComboBoxTextBoxStyle}"/>
<ToggleButton Grid.Column="1"
Height="{TemplateBinding Height}"
Style="{StaticResource ComboBoxButtonStyle}"
Focusable="False"
IsChecked="{Binding Path=IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
ClickMode="Press">
<Path Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Data="M 0 0 L 4 4 L 8 0 Z"
Fill="Black" />
</ToggleButton>
<ContentPresenter Grid.Column="0"
Name="ContentSite"
Content="{TemplateBinding SelectionBoxItem}"
ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}"
ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Margin="6,0,0,0"/>
<Popup Grid.Column="0"
Name="Popup"
Placement="Bottom"
IsOpen="{TemplateBinding IsDropDownOpen}"
AllowsTransparency="True"
Focusable="False"
PopupAnimation="Slide">
<Grid Name="DropDown"
SnapsToDevicePixels="True"
MinWidth="{TemplateBinding ActualWidth}"
MaxHeight="{TemplateBinding MaxDropDownHeight}">
<Border x:Name="DropDownBorder"
BorderThickness="1"
CornerRadius="2"
Background="White"
BorderBrush="#A0A1A2"/>
<ScrollViewer SnapsToDevicePixels="True">
<StackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Contained" />
</ScrollViewer>
</Grid>
</Popup>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
There were a few problems here. Each item I changed in the XAML is commented.
I've also removed the IsMouseOver triggers from the togglebutton and textbox styles, and put it in the ComboBox style. It now sets the background for the whole combobox when the mouse is anywhere over the ComboBox. The two controls in the ComboBox template now have Background="{TemplateBinding Background}", so they'll use the background color set by that trigger.
I've restored IsHitTestVisible on the textbox, but used a TemplateBinding to bind it to the ComboBox's IsEditable property. This will give you the correct mouse pointer over the textbox.
<!--
Better to define this in one place.
I'd do the same with the border color that you use everywhere.
-->
<SolidColorBrush x:Key="ComboBackgroundBrush" Color="#EAEAEA" />
<Style x:Key="ComboBoxTextBoxStyle" TargetType="{x:Type TextBox}">
<!--
Must set this in a setter, not an an attribute on the control instance.
The attribute you had will override anything the style does. This is part of
"dependency property value precedence".
-->
<Setter Property="Background" Value="{StaticResource ComboBackgroundBrush}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Grid>
<Border CornerRadius="2,0,0,2"
BorderThickness="1,1,0,1"
Background="{TemplateBinding Background}"
BorderBrush="#A0A1A2"
>
<ScrollViewer x:Name="PART_ContentHost"/>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ComboBoxButtonStyle" TargetType="{x:Type ToggleButton}">
<Setter Property="Background" Value="{StaticResource ComboBackgroundBrush}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<!--
Needs {TemplateBinding Background} so it uses whatever background brush
the control has at any given moment.
-->
<Border Background="{TemplateBinding Background}"
x:Name="border"
CornerRadius="0,2,2,0"
BorderThickness="0,1,1,1"
BorderBrush="#A0A1A2">
<ContentPresenter />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ComboBoxOverlayToggleButtonStyle" TargetType="ToggleButton">
<Setter Property="Focusable" Value="False" />
<Setter Property="ClickMode" Value="Press" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Grid Background="Transparent" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="RoundComboBox" TargetType="{x:Type ComboBox}" BasedOn="{StaticResource BorderStyle}">
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="FontSize" Value="14px"/>
<Setter Property="IsReadOnly" Value="True"/>
<Setter Property="Height" Value="25"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ComboBox}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition MaxWidth="18"/>
</Grid.ColumnDefinitions>
<ToggleButton
Grid.Column="0"
Style="{StaticResource ComboBoxOverlayToggleButtonStyle}"
IsChecked="{Binding Path=IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
/>
<!--
Two problems:
1. IsHitTestVisible="False" prevented it from getting any mouse messages.
2. Background attribute was overriding anything the Style did,
so even if the trigger had fired, its setter would have failed.
Also, Height="{TemplateBinding Height}" is unnecessary. It'll size to its parent Grid.
And BorderBrush="#A0A1A2" should probably be in the Style
-->
<TextBox Name="PART_EditableTextBox"
Padding="0,0,0,0"
BorderBrush="#A0A1A2"
Style="{StaticResource ComboBoxTextBoxStyle}"
Background="{TemplateBinding Background}"
IsHitTestVisible="{TemplateBinding IsEditable}"
/>
<ToggleButton Grid.Column="1"
Height="{TemplateBinding Height}"
Style="{StaticResource ComboBoxButtonStyle}"
Focusable="False"
IsChecked="{Binding Path=IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
ClickMode="Press"
Background="{TemplateBinding Background}"
>
<Path Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Data="M 0 0 L 4 4 L 8 0 Z"
Fill="Black" />
</ToggleButton>
<ContentPresenter Grid.Column="0"
Name="ContentSite"
Content="{TemplateBinding SelectionBoxItem}"
ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}"
ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Margin="6,0,0,0"
/>
<Popup Grid.Column="0"
Name="Popup"
Placement="Bottom"
IsOpen="{TemplateBinding IsDropDownOpen}"
AllowsTransparency="True"
Focusable="False"
PopupAnimation="Slide">
<Grid Name="DropDown"
SnapsToDevicePixels="True"
MinWidth="{TemplateBinding ActualWidth}"
MaxHeight="{TemplateBinding MaxDropDownHeight}">
<Border x:Name="DropDownBorder"
BorderThickness="1"
CornerRadius="2"
Background="White"
BorderBrush="#A0A1A2"/>
<ScrollViewer SnapsToDevicePixels="True">
<StackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Contained" />
</ScrollViewer>
</Grid>
</Popup>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Background" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>

Set the content of the textblock within toggle button

I am trying to set the content of the textblock (as shown below) when the IsChecked property has changed for the toggle button. I have tried multiple ways but unable to get it to work.
<ToggleButton VerticalAlignment="Center" Margin="4 0 0 0" Background="{DynamicResource AccentColorBrush}" Name="toggle">
<StackPanel Orientation="Horizontal">
<Rectangle Width="20" Height="20" Fill="{Binding RelativeSource={RelativeSource AncestorType=Button}, Path=Foreground}">
<Rectangle.OpacityMask>
<VisualBrush Stretch="Fill" Visual="{StaticResource appbar_starwars_jedi}" />
</Rectangle.OpacityMask>
</Rectangle>
<TextBlock x:Name="SimulationToggleButtonText" Margin="4 0 0 0" VerticalAlignment="Center" Text="Start Simulation">
</TextBlock>
</StackPanel>
<ToggleButton.Style>
<Style TargetType="{x:Type ToggleButton}">
<Setter Property="Foreground" Value="Green"/>
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Content" Value="Stop Simulation"/>
<Setter Property="Foreground" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
</ToggleButton.Style>
</ToggleButton>
You need to use a control template, please see this adjusted code:
<ToggleButton VerticalAlignment="Center" Margin="4 0 0 0" Background="{DynamicResource AccentColorBrush}" Name="toggle">
<ToggleButton.Style>
<Style TargetType="{x:Type ToggleButton}">
<Setter Property="Foreground" Value="Green"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<StackPanel Orientation="Horizontal">
<Rectangle Width="20" Height="20" Fill="{Binding RelativeSource={RelativeSource AncestorType=Button}, Path=Foreground}">
<Rectangle.OpacityMask>
<VisualBrush Stretch="Fill" Visual="{StaticResource appbar_starwars_jedi}" />
</Rectangle.OpacityMask>
</Rectangle>
<TextBlock x:Name="SimulationToggleButtonText" Margin="4 0 0 0" VerticalAlignment="Center" Text="Start Simulation">
</TextBlock>
</StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="SimulationToggleButtonText" Property="Text" Value="Stop Simulation"/>
<Setter TargetName="SimulationToggleButtonText" Property="Foreground" Value="Red"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ToggleButton.Style>
</ToggleButton>
The problem is that your ToggleButton style doesn't have access to any controls that are a part of the ToggleButton content. By moving the controls and triggers to the template, they are in the same scope.

WPF Progressbar with images and fill effect

I'd like to create custom progressbar style that will display image filling up from bottom.
I've created two images, background:
and foreground:
Idea is to create something like this:
Inside Blend I've created this style:
<Style x:Key="ImageFill" TargetType="{x:Type ProgressBar}">
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ProgressBar}">
<Grid x:Name="TemplateRoot" SnapsToDevicePixels="true">
<Image x:Name="PART_Track" Source="Play_Background.png" Margin="1" Stretch="Fill"/>
<Rectangle x:Name="PART_Indicator" Margin="1" HorizontalAlignment="Left" Fill="#FFD6931C">
<Rectangle.OpacityMask>
<RadialGradientBrush>
<GradientStop Color="Black" Offset="0.87"/>
<GradientStop Color="Transparent" Offset="0.87"/>
</RadialGradientBrush>
</Rectangle.OpacityMask>
</Rectangle>
<Image Source="Play_Foreground.png" Margin="1" Stretch="Fill"/>
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="2"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
But when setting value to something like 60 I get this:
I can change OpacitMmask to this:
<RadialGradientBrush Center="106,104" GradientOrigin="60,60" MappingMode="Absolute" RadiusY="97" RadiusX="98">
<GradientStop Color="Black" Offset="0.87"/>
<GradientStop Color="Transparent" Offset="0.87"/>
</RadialGradientBrush>
but then when I resize my progress bar I get unwanted behaviour:
How this can be fixed? I need mask to have MappingMode set to RelativeToBoundingBox so I can set different size to progressbar.
Below is full XAML I've generated in Blend:
<Window x:Class="ImageProgressBar.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="389" Width="523">
<Window.Resources>
<Style x:Key="ImageFill" TargetType="{x:Type ProgressBar}">
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ProgressBar}">
<Grid x:Name="TemplateRoot" SnapsToDevicePixels="true">
<Image x:Name="PART_Track" Source="Play_Background.png" Margin="1" Stretch="Fill"/>
<Rectangle x:Name="PART_Indicator" Margin="1" HorizontalAlignment="Left" Fill="#FFD6931C">
<Rectangle.OpacityMask>
<RadialGradientBrush Center="106,104" GradientOrigin="60,60" MappingMode="Absolute" RadiusY="97" RadiusX="98">
<GradientStop Color="Black" Offset="0.87"/>
<GradientStop Color="Transparent" Offset="0.87"/>
</RadialGradientBrush>
</Rectangle.OpacityMask>
</Rectangle>
<Image Source="Play_Foreground.png" Margin="1" Stretch="Fill"/>
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="2"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<ProgressBar
Maximum="{Binding ElementName=MySlider, Path=Maximum, Mode=TwoWay}" Minimum="{Binding ElementName=MySlider, Path=Minimum, Mode=TwoWay}" Value="{Binding ElementName=MySlider, Path=Value, Mode=TwoWay}"
HorizontalAlignment="Left" Height="200" Margin="10,10,0,0" VerticalAlignment="Top" Width="200" Style="{DynamicResource ImageFill}"/>
<ProgressBar
Maximum="{Binding ElementName=MySlider, Path=Maximum, Mode=TwoWay}" Minimum="{Binding ElementName=MySlider, Path=Minimum, Mode=TwoWay}" Value="{Binding ElementName=MySlider, Path=Value, Mode=TwoWay}"
HorizontalAlignment="Left" Height="100" Margin="294,10,0,0" VerticalAlignment="Top" Width="100" Style="{DynamicResource ImageFill}"/>
<Slider Name="MySlider" HorizontalAlignment="Left" Margin="10,215,0,0" VerticalAlignment="Top" Width="200" Minimum="0" Maximum="100" Value="0"/>
</Grid>
</Window>
I've found http://vbcity.com/blogs/xtab/archive/2009/11/24/wpf-controltemplates-creating-a-non-rectangular-progressbar.aspx but can't use this inside my style.
I've managed to create style that has effect I wanted.
Below is how it looks:
and below is working code with style and slider to change value of progressbar:
<?xml version="1.0" encoding="UTF-8"?>
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="ImageProgressBar.MainWindow" Title="ProgressBar Image Fill" Height="284" Width="598" Background="#FFEAE0E0">
<Window.Resources>
<Style x:Key="ImageFill" TargetType="{x:Type ProgressBar}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ProgressBar}">
<Grid x:Name="TemplateRoot" SnapsToDevicePixels="true">
<Image x:Name="PART_Track" Source="Play_Background.png" Margin="1" Stretch="Fill" />
<Rectangle x:Name="PART_Indicator" Margin="1" HorizontalAlignment="Left" Fill="{TemplateBinding Foreground}">
<Rectangle.OpacityMask>
<RadialGradientBrush Center="106,104" GradientOrigin="60,60" MappingMode="Absolute" RadiusY="97" RadiusX="98">
<GradientStop Color="Black" Offset="0.87" />
<GradientStop Color="Transparent" Offset="0.87" />
</RadialGradientBrush>
</Rectangle.OpacityMask>
</Rectangle>
<Image Source="Play_Foreground.png" Margin="1" Stretch="Fill" />
</Grid>
<ControlTemplate.Triggers>
<!-- Getting vertical style working using technique described here: http://stackoverflow.com/a/6849237/7532 -->
<Trigger Property="Orientation" Value="Vertical">
<Setter TargetName="PART_Indicator" Property="LayoutTransform">
<Setter.Value>
<RotateTransform Angle="270" />
</Setter.Value>
</Setter>
<Setter TargetName="PART_Indicator" Property="Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Height}" />
<Setter TargetName="PART_Indicator" Property="Height" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Width}" />
<Setter TargetName="PART_Indicator" Property="VerticalAlignment" Value="Bottom" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="SimpleImageFill" TargetType="{x:Type ProgressBar}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ProgressBar}">
<Grid x:Name="TemplateRoot" SnapsToDevicePixels="true">
<Image x:Name="PART_Track" Source="Play_Game_Empty.png" />
<Canvas ClipToBounds="True" x:Name="PART_Indicator" HorizontalAlignment="Left">
<Image x:Name="Image_Fill" Source="Play_Game_Fill.png"
Width="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Width}"
Height="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Height}" />
</Canvas>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="Orientation" Value="Vertical">
<Setter TargetName="PART_Indicator" Property="LayoutTransform">
<Setter.Value>
<RotateTransform Angle="270" />
</Setter.Value>
</Setter>
<Setter TargetName="Image_Fill" Property="LayoutTransform">
<Setter.Value>
<RotateTransform Angle="-270" />
</Setter.Value>
</Setter>
<Setter TargetName="PART_Indicator" Property="Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Height}" />
<Setter TargetName="PART_Indicator" Property="Height" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Width}" />
<Setter TargetName="PART_Indicator" Property="VerticalAlignment" Value="Bottom" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<ProgressBar Foreground="Orange" Orientation="Vertical" Maximum="{Binding ElementName=MySlider, Path=Maximum, Mode=TwoWay}" Minimum="{Binding ElementName=MySlider, Path=Minimum, Mode=TwoWay}" Value="{Binding ElementName=MySlider, Path=Value, Mode=TwoWay}" HorizontalAlignment="Left" Height="200" Margin="10,10,0,0" VerticalAlignment="Top" Width="200" Style="{DynamicResource ImageFill}" />
<ProgressBar Maximum="{Binding ElementName=MySlider, Path=Maximum, Mode=TwoWay}" Minimum="{Binding ElementName=MySlider, Path=Minimum, Mode=TwoWay}" Value="{Binding ElementName=MySlider, Path=Value, Mode=TwoWay}" HorizontalAlignment="Left" Height="200" Margin="227.5,10,0,0" VerticalAlignment="Top" Width="200" Style="{DynamicResource ImageFill}" />
<Slider Name="MySlider" HorizontalAlignment="Left" Margin="10,215,0,0" VerticalAlignment="Top" Width="200" Minimum="0" Maximum="100" Value="0" />
<ProgressBar Orientation="Vertical" Maximum="{Binding ElementName=MySlider, Path=Maximum, Mode=TwoWay}" Minimum="{Binding ElementName=MySlider, Path=Minimum, Mode=TwoWay}" Value="{Binding ElementName=MySlider, Path=Value, Mode=TwoWay}" HorizontalAlignment="Left" Height="150" Margin="432.5,10,0,0" VerticalAlignment="Top" Width="150" Style="{DynamicResource SimpleImageFill}" />
</Grid>
</Window>
I've actually created two styles:
First one is using two images (top two from my question) and circle
between them. Idea was to fill circle from bottom or from left.
Circle is filled with foreground color.
Second is much simpler, it uses only two images (empty and filled). When value changes filled image, that is on top of empty, is shown from left or from bottom. I've used canvas that is filled with image and I change it width or height.
I'm just starting with WPF, so is someone knows a better solution please post answer.
Below I'm adding two images used by second style applied to third progressbar.
Check this out WPF: Anchor points don't work well sometimes The problem is that your sizing and margins are absolutes not relative the the size of your control.

CheckComboBox : How to prevent a combobox from closing after a selection is clicked?

I've got a combobox and it's populated with a bunch of checkboxes. I wish the user to be able to click multiple times before the combobox closes (or is closed by the user themselves). The problems right now is that each time a checkbox is clicked, the combobox closes, forcing the user who wants to select multiple options to re-open it several times.
I've found other questions on the same subject but those apply to Silverlight, Qt etc. comparing the tags.
I've tried setting StayOpenOnEdit but that didn't do the trick. As far I could see, there's no property addressing my issue. That creates a suspicion that I might be barning up the wrong component all together.
How do I prevent the combobox to close automatically after a click in a checkbox in it?
Is there a more suitable component for such task and, if so, what's its name?
Please note that even if the answer to #2 is "yes", I'm still curious of #1 for purely academic reasons.
I've played with toggle button and list box, as someone suggested in a post. However, the that led only to an always fully shown list of all the checkboxes with some greyish thing behind it (which I'm assuming is the toggler). Perhaps I did something less clever in the mark-up.
<ToggleButton HorizontalAlignment="Left"
Margin="550,62,0,0"
VerticalAlignment="Top"
Width="100">
<ListBox x:Name="listBox1"
HorizontalAlignment="Left"
Height="100"
VerticalAlignment="Top" Width="100">
<CheckBox x:Name="checkBox3" Content="CheckBox"/>
<CheckBox x:Name="checkBox4" Content="CheckBox"/>
</ListBox>
</ToggleButton>
The point is to achieve something like this but it needs to be a standard WPF control (the concatenated line of all selected items is nice but not a must). Also, I've read the complaints that the binding and handling is not fully developed yet and I feel a bit suspicious.
Using Exapander Control you can achieve multple item selection without closing popup after single selection.
For understanding please run this code separately.
xaml
<Window.Resources>
<ControlTemplate x:Key="ComboboxToggleButton" TargetType="{x:Type ToggleButton}">
<Grid Background="{Binding Background,RelativeSource={RelativeSource AncestorType={x:Type ComboBox}}}">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="35" />
</Grid.ColumnDefinitions>
<Border x:Name="Border" Grid.ColumnSpan="2" Background="Transparent" BorderBrush="Black" BorderThickness="{Binding BorderThickness,RelativeSource={RelativeSource TemplatedParent}}"/>
<Path x:Name="Arrow" Grid.Column="1" Opacity="1" Stroke="Black" StrokeThickness="1.5" HorizontalAlignment="Center" VerticalAlignment="Center" Data="M 0 0 L 6 6 L 12 0" />
<Path x:Name="Arrow_checked" Opacity="0" Grid.Column="1" Fill="Black" Stroke="Black" StrokeThickness="1" HorizontalAlignment="Center" VerticalAlignment="Center" Data="M 0 0 L 6 6 L 12 0 Z" />
<ContentPresenter TextElement.FontFamily="Segoe Ui Dark" TextElement.FontSize="18" TextElement.Foreground="Black" VerticalAlignment="Center" Grid.Column="0" Margin="10,0,0,0" HorizontalAlignment="Left" RecognizesAccessKey="True" SnapsToDevicePixels="True" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="ToggleButton.IsMouseOver" Value="true">
<Setter TargetName="Border" Property="Background" Value="Gray" />
<Setter TargetName="Border" Property="BorderThickness" Value="1.2" />
</Trigger>
<Trigger Property="IsChecked" Value="False">
<Setter Property="Opacity" Value="1" TargetName="Arrow"/>
<Setter Property="Opacity" Value="0" TargetName="Arrow_checked"/>
</Trigger>
<Trigger Property="ToggleButton.IsChecked" Value="true">
<Setter Property="Opacity" Value="0" TargetName="Arrow"/>
<Setter Property="Opacity" Value="1" TargetName="Arrow_checked"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="Border" Property="Background" Value="Gray" />
<Setter TargetName="Border" Property="BorderBrush" Value="White" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<Style TargetType="{x:Type Expander}">
<Setter Property="FontFamily" Value="Segoe Ui Dark"></Setter>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="MaxHeight" Value="200"></Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Expander}">
<DockPanel>
<ToggleButton x:Name="HeaderSite" Height="35" Background="{TemplateBinding Background}" ContentTemplate="{TemplateBinding HeaderTemplate}" ContentTemplateSelector="{TemplateBinding HeaderTemplateSelector}" Content="{TemplateBinding Header}" DockPanel.Dock="Top" Foreground="Black" FontStyle="{TemplateBinding FontStyle}" FontFamily="Segoe UI Dark"
IsChecked="{Binding IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Template="{StaticResource ComboboxToggleButton}" />
<Border BorderThickness="0,4.5,0,0" BorderBrush="Transparent">
<Border x:Name="bod" BorderBrush="Transparent" SnapsToDevicePixels="True" BorderThickness="1">
<ContentPresenter x:Name="ExpandSite" Focusable="false" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Visibility="Collapsed" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
</Border>
</Border>
</DockPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="true">
<Setter Property="Visibility" TargetName="ExpandSite" Value="Visible"/>
<Setter Property="BorderBrush" TargetName="bod" Value="Black"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="Gray"></Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="itemstyle" TargetType="{x:Type ListBoxItem}">
<Setter Property="SnapsToDevicePixels" Value="true" />
<Setter Property="OverridesDefaultStyle" Value="true" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Grid x:Name="Border" Height="40" SnapsToDevicePixels="true">
<Grid.Background>
<SolidColorBrush Color="Transparent" />
</Grid.Background>
<ContentPresenter Name="cmb_name" TextElement.FontFamily="Segoe Ui Dark" TextElement.FontSize="18" TextElement.Foreground="Black" Margin="10,0,0,0" VerticalAlignment="Center"></ContentPresenter>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Border" Property="Background" Value="Gray"></Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="FocusVisualStyle">
<Setter.Value>
<Style TargetType="Control">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border BorderBrush="{DynamicResource customBlueBrush}" BorderThickness="1" Margin="1,2,2,2" >
<Rectangle Fill="{DynamicResource customBlueBrush}" Opacity="0.1"></Rectangle>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="CheckBox" >
<Setter Property="SnapsToDevicePixels" Value="True"></Setter>
<Setter Property="OverridesDefaultStyle" Value="True"></Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="CheckBox">
<Grid x:Name="ab" Background="Transparent">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Rectangle VerticalAlignment="Center" Height="20" Width="20" Fill="White" HorizontalAlignment="Left"></Rectangle>
<Grid x:Name="checkGrid" VerticalAlignment="Center" Height="20" Width="20" Background="Black" HorizontalAlignment="Left">
<Viewbox Height="13" Width="13">
<Path x:Name="Check" SnapsToDevicePixels="True" UseLayoutRounding="True" Width="18.7969" Height="16.3094" Canvas.Left="0" Canvas.Top="1.52588e-005" Stretch="Fill" Fill="White" Data="F1 M 0.731262,8.75935L 0.106262,8.08437C 0.0354614,7.9948 0,7.8979 0,7.79375C 0,7.66875 0.0479736,7.5573 0.143799,7.45937L 1.94067,5.77187C 2.02606,5.69893 2.12708,5.66249 2.24377,5.66249C 2.30212,5.66249 2.36096,5.67397 2.42035,5.69685C 2.47974,5.71977 2.52814,5.75417 2.56567,5.79997L 7.5188,11.1406L 16.0438,0.165604C 16.1417,0.055191 16.2584,1.52588e-005 16.3938,1.52588e-005C 16.4979,1.52588e-005 16.5896,0.0322723 16.6688,0.0968475L 18.6313,1.60939C 18.6709,1.64272 18.7084,1.69011 18.7438,1.75154C 18.7792,1.813 18.7969,1.8698 18.7969,1.92189C 18.7969,2.03435 18.7646,2.1385 18.7,2.23439L 7.74377,16.3094L 0.731262,8.75935 Z " />
</Viewbox>
</Grid>
<Grid Background="Transparent" Grid.Column="1" IsHitTestVisible="True" HorizontalAlignment="Stretch">
<TextBlock VerticalAlignment="Center" FontSize="18" FontFamily="Segoe Ui Dark" Foreground="Black" TextTrimming="CharacterEllipsis">
<ContentPresenter></ContentPresenter>
</TextBlock>
</Grid>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Opacity" Value="1" TargetName="Check"></Setter>
</Trigger>
<Trigger Property="IsChecked" Value="False">
<Setter Property="Opacity" Value="0" TargetName="Check"></Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="FocusVisualStyle">
<Setter.Value>
<Style TargetType="Control">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border BorderBrush="{DynamicResource customBlueBrush}" SnapsToDevicePixels="True" BorderThickness="1" Margin="-5,1,3,1" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid x:Name="gd" >
<Expander Width="500">
<Expander.Header>
<ListBox Background="Transparent" IsHitTestVisible="False" BorderBrush="Transparent" ScrollViewer.HorizontalScrollBarVisibility="Hidden" ScrollViewer.VerticalScrollBarVisibility="Disabled" BorderThickness="0" ItemsSource="{Binding ElementName=lst,Path=SelectedItems}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"></WrapPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ContentData}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Expander.Header>
<Expander.Content>
<ListBox Background="Transparent" ItemContainerStyle="{StaticResource itemstyle}" HorizontalContentAlignment="Stretch" x:Name="lst" SelectionMode="Multiple">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox x:Name="checkBox" IsChecked="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}, Path=IsSelected}" Content="{Binding ContentData}"></CheckBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Expander.Content>
</Expander>
</Grid>
c# code
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
ObservableCollection<Customer> custdata = new ObservableCollection<Customer>();
custdata.Add(new Customer() { ContentData = "content1" });
custdata.Add(new Customer() { ContentData = "content2" });
custdata.Add(new Customer() { ContentData = "content3" });
custdata.Add(new Customer() { ContentData = "content4" });
custdata.Add(new Customer() { ContentData = "content5" });
custdata.Add(new Customer() { ContentData = "content6" });
lst.ItemsSource = custdata;
}
}
public class Customer
{
public string ContentData { get; set; }
}
Result
Konrad,
I also decided to use a ComboBox in this manner, because the code was just dead simple. The easiest way I have found to keep the ComboBox popup open is to wire-up to the PreviewMouseDown event of the control in the ComboBox's item template. handle the behavior yourself, and then mark the mouse event handled. Works great for me. In the sample below, each object in FilterItems is a simple view model with a Text property and an IsChecked property.
<ComboBox IsEditable="True" IsReadOnly="False" ItemsSource="{Binding FilterItems}">
<ComboBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsChecked}" Content="{Binding Text}"
PreviewMouseDown="FilterComboBox_PreviewMouseDown"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
And then my event handler is:
private void FilterComboBox_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
var cb = sender as CheckBox;
if (cb != null)
{
cb.IsChecked = !cb.IsChecked;
e.Handled = true;
}
}

How do I access an element inside a style programmatically?

I have a WPF button style below:
<Style x:Key="myButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid Width="{Binding Width}" Height="{Binding Height}">
<Border Name="container" Background="{Binding Background}" CornerRadius="{Binding CornerRadius}">
<TextBlock Margin="10" FontFamily="Arial" FontWeight="Bold" TextAlignment="Left" HorizontalAlignment="Left" VerticalAlignment="Top" Width="Auto" FontSize="{Binding TextSize}" Foreground="White" Text="{Binding Text}" TextWrapping="Wrap"/>
</Border>
<Border Name="overlay" Background="Transparent" CornerRadius="{Binding CornerRadius}" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="overlay" Property="Opacity" Value="0.6" />
<Setter TargetName="overlay" Property="Background" Value="Black" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The problem is that, although all of the buttons of my application have the same style, some of them need to have a border around it. Therefore I would like to know if it is possible to access the container border of my style to set its thickness and color? If so how can I do this?
Edit:
I've mixed the suggestions of madd0 and Josh and created a DataTrigger inside my style with a binding to a property that tells me if the button should or should not have a border.
The final code is below:
<Style x:Key="myButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid Width="{Binding Width}" Height="{Binding Height}">
<Border Name="container" Background="{Binding Background}" CornerRadius="{Binding CornerRadius}">
<TextBlock Margin="10" FontFamily="Arial" FontWeight="Bold" TextAlignment="Left" HorizontalAlignment="Left" VerticalAlignment="Top" Width="Auto" FontSize="{Binding TextSize}" Foreground="White" Text="{Binding Text}" TextWrapping="Wrap"/>
</Border>
<Border Name="overlay" Background="Transparent" CornerRadius="{Binding CornerRadius}" />
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Path=HasBorder}" Value="true">
<Setter TargetName="container" Property="BorderThickness" Value="{Binding BorderThickness}" />
<Setter TargetName="container" Property="BorderBrush" Value="{Binding BorderBrush}" />
</DataTrigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="overlay" Property="Opacity" Value="0.6" />
<Setter TargetName="overlay" Property="Background" Value="Black" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Thank you all for your help,
Komyg
I think if you extend the Button class and give it a new boolean DependencyProperty, essentially all you need to do is give your Border a name, then in ControlTemplate.Triggers, Trigger off that boolean property to make the border as you need it in your special cases.
It would be similar to what you already have with the IsPressed ControlTemplate trigger.
I don't think you'll really need to access your control template properties. Since Button already has border properties, you ought to just set those properties on the button directly. Then, add the border to your ControlTemplate, binding its properties to that of the button.

Categories

Resources