ComboBox highlighting - wpf

I have a problem with my combobox I'm trying to customize. It is in a UserControl, and i want its BorderBrush property to change from Transparent to White when the mouse is over it (fade in/out would be bonus).
But I can't seem to get the proper Trigger syntax to do so... now I feel confused and I'm probably missing something obvious here.
Here is the combobox in question:
<ComboBox x:Name="comboEmiCategories" ItemsSource="{Binding}" Background="Transparent" Height="15px" Width="30px" BorderBrush="Transparent" Padding="-2">
<ComboBox.Resources>
<sys:Double x:Key="{x:Static SystemParameters.VerticalScrollBarWidthKey}">0</sys:Double>
</ComboBox.Resources>
<ComboBox.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}" Stretch="UniformToFill" Height="15px" Width="30px" Margin="0" />
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.Template>
<ControlTemplate>
<ControlTemplate.Triggers>
<Trigger Property="ComboBox.IsMouseOver" Value="True">
<Setter Property="ComboBox.BorderBrush" Value="White" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</ComboBox.Template>
</ComboBox>
The trigger isn't working, even worse actually, if i don't comment out the whole ComboBox.Template part, the control disappears.
The main goal would be to have a ComboBox that stacks images and allows the user to select one out of a list, with nothing else than those images shown.
Thanks.
EDIT:
Mario's solution of putting it within a style works, but is it the only way to do this?

Try to place the below xaml within the Resources section of your window/usercontrol.
<Style x:Name="cbStyle" TargetType="ComboBox">
<Setter Property="BorderBrush" Value="Transparent" />
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="BorderBrush" Value="White" />
</Trigger>
</Style.Triggers>
</Style>
Of course you must refer this style in the ComboBox element. Also cut anything within the ControlTemplate, which is useless.
EDIT: your ComboBox section should look as follows:
<ComboBox x:Name="comboEmiCategories" ItemsSource="{Binding}" Height="15px" Width="30px" Style="{StaticResource cbStyle}" Padding="-2">
<ComboBox.Resources>
<sys:Double x:Key="{x:Static SystemParameters.VerticalScrollBarWidthKey}">0</sys:Double>
</ComboBox.Resources>
<ComboBox.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}" Stretch="UniformToFill" Height="15px" Width="30px" Margin="0" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Also put the Style declatarion in your UserControl.

Related

Editing the property of an element within the ControlTemplate in WPF

I have some radio buttons that I'm building a custom control template for. Image of the buttons:
In the control template, each radio button will have a textblock with its name and another textblock below it to indicate if it's unavailable.
I want the "Unavailable" text to be visible ONLY when the button is NOT enabled. When the radio button is ENABLED, the "Unavailable" textblock should be collapsed.
Here is the simplified view.xaml for the buttons:
<RadioButton Name="one"
IsEnabled="{Binding One_isAvailable}"
Style="{StaticResource RadioButtonTheme}" />
<RadioButton Name="two"
IsEnabled="{Binding Two_isAvailable}"
Style="{StaticResource RadioButtonTheme}" />
<RadioButton Name="three"
IsEnabled="{Binding Three_isAvailable}"
Style="{StaticResource RadioButtonTheme}"/>
Here is the simplified version of the styling I have so far (RadioButtonTheme.xaml):
<ResourceDictionary>
<Style BasedOn="{StaticResource {x:Type ToggleButton}}"
TargetType="{x:Type RadioButton}"
x:Key="RadioButtonTheme">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border CornerRadius="7">
<StackPanel VerticalAlignment="Center">
<TextBlock HorizontalAlignment="Center"
Text="{TemplateBinding Property=Name}"
Foreground="{TemplateBinding Property=Foreground}">
</TextBlock>
<TextBlock Name="UnavailableTextBlock"
HorizontalAlignment="Center"
Text="Unavailable"
FontSize="14"
FontStyle="Italic"
Foreground="{TemplateBinding Property=Foreground}">
</TextBlock>
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
So I've tried setting a couple things:
I set a visiblitity property on the radio button on the view.xaml. I then binded that visibility to the "UnavailableTextBlock" in the radiobuttontheme.xaml and set the rest of the template visiblity to "Visible." I thought that I can leave the template visible except for one element of it. I now don't think that's possible.
I tried directly binding the "UnavailableTextBlock" to the IsEnabled property of the radiobutton, and ran it through a BoolToVisiblityConverter.
<TextBlock Name="UnavailableTextBlock"
Visibility="{TemplateBinding Property=IsEnabled, Converter={StaticResource BoolToVisConverter}}">
However, I can't seem to get my converter to work inside of the ResourceDictionary. The program will crash with the error: "Cannot find resource named 'BoolToVisConverter'. Resource names are case sensitive"
I have this converter working across my other xaml files since I added it to my <Application.Resources> in the app.xaml. Do I need to link my Resource dictionary to the converter? How do I do that? <ResourceDictionary.Resources> didn't seem to work for me.
I tried adding a datatrigger to the "UnavailableTextBlock" as below:
<TextBlock Name="UnavailableTextBlock"....>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{TemplateBinding Property=IsEnabled}" Value="false">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
However, I get an error saying: '"IsEnabled" member is not valid because it does not have a qualifying type name.'
I'm guessing that it's referencing the IsEnabled property of the TextBlock and not of the radio button? Although I'm not too sure. I'm still learning WPF.
Thanks for all your help in advance!
If I understand correctly what you want to implement, then you need to use the control template trigger.
<Style BasedOn="{StaticResource {x:Type ToggleButton}}"
TargetType="{x:Type RadioButton}"
x:Key="RadioButtonTheme">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="RadioButton">
<Border CornerRadius="7">
<StackPanel VerticalAlignment="Center">
<TextBlock HorizontalAlignment="Center"
Text="{TemplateBinding Property=Name}"
Foreground="{TemplateBinding Property=Foreground}">
</TextBlock>
<TextBlock Name="UnavailableTextBlock"
HorizontalAlignment="Center"
Text="Unavailable"
FontSize="14"
FontStyle="Italic"
Foreground="{TemplateBinding Property=Foreground}">
</TextBlock>
</StackPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="UnavailableTextBlock" Property="Visibility" Value="Hidden"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

Bind header textblock isEnabled to parent Groupbox isEnabled

Referencing to this question: WPF Databinding: How do I access the "parent" data context?
I wanna do something similiar, but for the header of a Groupbox (because the header does not concern with the Box is being disabled and thus is always black while the rest is light gray. This looks a bit strange to me if all the content of the box is gray, the above is gray, but the box title itself stays black.
So I tried to use the approach mentioned in the linked question by flq to simply bind the isEnabled property of the header textblock to the isEnabled property of the groupbox but it seems that my binding at some point fails and I don't know where and why exactly.
heres my current code:
<GroupBox Header="Change Steps" Grid.Row="2" Grid.ColumnSpan="3" Name="gbChangeSteps">
<GroupBox.Style>
<Style TargetType="GroupBox">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding}" FontWeight="Bold" Height="19" Foreground="Black" IsEnabled="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type GroupBox}}, Path=isEnabled}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupBox.Style>
<!-- ... (some non relevant Content)-->
</GroupBox>
after additional research I found the post Disable groupBox including the groupBox name in WPF
that lead me, in combination with Properties->Create Databinding->Binding type->UIElement to the solution that fixed both problems, the one this question was about and the original one that lead to entire restyling, which was that letters like the small g got messed up in the header.
This is the code that fixed the issue:
<GroupBox.Style>
<Style TargetType="{x:Type GroupBox}">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding}" FontWeight="Bold" Height="19" IsEnabled="{Binding IsEnabled, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UIElement}}}">
<TextBlock.Style>
<Style>
<Style.Triggers>
<Trigger Property="Control.IsEnabled" Value="False">
<Setter Property="Control.Foreground" Value ="#FF6D6D6D" />
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupBox.Style>

Excess border selection in WPF's Lisbox [duplicate]

I have a ListBox in which each item is a StackPanel. The StackPanel consist of an Image and a TextBlock below it:
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="10">
<Image>
<Image.Source>
<BitmapImage UriSource="{Binding Path=ImageFilePath}"/>
</Image.Source>
</Image>
<TextBlock Text="Title" TextAlignment="Center"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
It looks like this:
When the user select an item, I get the default blue rectangle that surround the StackPanel:
Now, I want to make a different border for the selected-item, but I want it to surround only the image.
I know how to make a control template and put a custom border around the ContentPresenter, but this, of course, will surround the whole StackPanel, not only the Image.
I don’t know if making changes to the ContentPresenter is possible, and if it is a good idea at all. If there is other way to achieve the look I want, it will be fine as well.
Right, the ListBox's own ContentPresenter isn't helpful for what you're doing. You want to a) eliminate the ListBox's own selection visuals and b) replace them with something more suitable in the DataTemplate for your items.
The default selection visual is applied by the default template for ListBoxItem. So replace that template. Using a Style in the resources for your ListBox, apply your own control template to ListBoxItem. Not much to it, just present the content and don't provide a selection background. Then you handle the selection visuals with a trigger in your data template, where your image and your label are defined and you can apply changes to one and not the other. The below example works for me.
Note that there's some fiddling with the HorizontalAlignment on the Border element to make it cling to the Image element within it. Also, I wrote a quickie test viewmodel whose Items property is called Items; I assume this is not the name of the collection member you're using to populate your own ListBox.
<ListBox
Margin="8"
ItemsSource="{Binding Items}"
>
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Grid>
<ContentPresenter />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Border
x:Name="HighlightBorder"
BorderThickness="4"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="10"
>
<Border.Style>
<Style TargetType="Border">
<!-- MUST set default BorderBrush via a style, if you set it at all.
As an attribute on the Border tag, it would override the effects of
the trigger below.
-->
<Setter Property="BorderBrush" Value="Transparent" />
</Style>
</Border.Style>
<Image Source="{Binding ImageFilePath}" />
</Border>
</Grid>
<DataTemplate.Triggers>
<DataTrigger
Binding="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListBoxItem}}"
Value="True">
<Setter TargetName="HighlightBorder" Property="BorderBrush" Value="Orange" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Binding the background colour of a control using a trigger in WPF/XAML

Okay, first off I have no experience of WPF whatsoever so please bear with me and apologies if my terminology is a little wayward... ;)
The following code snippet is part of a WPF application that I have inherited. The trigger governs whether mandatory fields on a particular form are highlighted or not. The code works but the highlighting seems to apply to the control and the border (??) which contains it.
<ItemsControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cal="clr-namespace:Caliburn.PresentationFramework.ApplicationModel;assembly=Caliburn.PresentationFramework"
x:Class="company.product.Jobs.JobParametersEditor"
IsTabStop="False">
<ItemsControl.ItemTemplate>
<DataTemplate>
<DockPanel MinHeight="30">
<TextBlock Text="{Binding DisplayName, Mode=OneWay}"
DockPanel.Dock="Left"
VerticalAlignment="Center"
MinWidth="120"
Margin="6,0" />
<Border>
<Border.Style>
<Style TargetType="{x:Type Border}">
<Setter Property="Background"
Value="{x:Null}" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsValid}"
Value="False">
<Setter Property="Background"
Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<ContentControl cal:View.Model="{Binding ValueEditor}"
ToolTip="{Binding ToolTip}"
IsTabStop="False"
MinHeight="19"
VerticalAlignment="Center"
HorizontalAlignment="Stretch" />
</Border>
</DockPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The result is a bit clunky so I would like to restrict the highlighting to the control only but I can't figure out how to do it. I've tried moving the trigger so that it applies to the ContentControl instead of the Border but that didn't work and fiddling about with border margins, padding and thickness hasn't had any effect either.
Could anybody enlighten me as to how to accomplish this?

How to propagate styles to Hyperlinks inside a DataTemplate?

I'm try to set the Foreground colour on a Hyperlink using a Style object in an ancestor's Resources, but it's not having any effect. I even used the BasedOn tip from Changing Hyperlink foreground without losing hover color, but it's not made any difference - I still get a blue hyperlink that is red on hover.
Here's the XAML for my controls, including an ItemsControl whose items are shown using a hyperlink:
<StackPanel Background="Red" TextElement.Foreground="White">
<StackPanel.Resources>
<Style TargetType="Hyperlink" BasedOn="{StaticResource {x:Type Hyperlink}}">
<Setter Property="Foreground" Value="Yellow"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="White"/>
</Trigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
<TextBlock>Data import errors</TextBlock>
<ItemsControl ItemsSource="{Binding Errors}"/>
</StackPanel>
And the items in the ItemsControl are picking up the following DataTemplate:
<DataTemplate DataType="{x:Type Importer:ConversionDetailsMessage}">
<TextBlock>
<Run Text="{Binding Message, Mode=OneTime}"/>
<Hyperlink Command="Common:ImportDataCommands.ExplainConversionMessage" CommandParameter="{Binding}">
<Run Text="{Binding HelpLink.Item2, Mode=OneTime}"/>
</Hyperlink>
</TextBlock>
</DataTemplate>
It's worth noting, too, that I don't want to just set the different colours directly on the Hyperlink in the DataTemplate. This is because the template will be used by a number of different ItemsControl objects, most of which will be on a white background and so can use the standard hyperlink colours. (Note that the one in the XAML above, however, has a red background.)
In short, I don't want the DataTemplate to have to know anything about the control in which it's being used. The styles for the template's controls should just filter down to it.
So... can anyone tell me why the style's not filtering down and what I can do to fix it?
Thanks.
Update:
Since I couldn't get Pavlo's answer to work in my production app, I've since tried it in a separate test app. The app is a WinForms app, with the main form containing nothing but an ElementHost, which itself contains a simple WPF usercontrol. Here's its XAML:
<UserControl x:Class="DataTemplateStyling.StylingView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:DataTemplateStyling="clr-namespace:DataTemplateStyling"
x:Name="root" Loaded="StylingViewLoaded">
<UserControl.Resources>
<Style x:Key="MyDefaultHyperlinkStyle" BasedOn="{StaticResource {x:Type Hyperlink}}"/>
<DataTemplate DataType="{x:Type DataTemplateStyling:ImportMessage}">
<DataTemplate.Resources>
<Style TargetType="{x:Type Hyperlink}"
BasedOn="{StaticResource MyDefaultHyperlinkStyle}"/>
</DataTemplate.Resources>
<TextBlock>
<Run Text="{Binding Message, Mode=OneTime}"/>
<Hyperlink NavigateUri="{Binding HelpLink.Item1}">
<Run Text="{Binding HelpLink.Item2, Mode=OneTime}"/>
</Hyperlink>
</TextBlock>
</DataTemplate>
</UserControl.Resources>
<Grid DataContext="{Binding ElementName=root}">
<StackPanel Background="Red" TextElement.Foreground="White">
<StackPanel.Resources>
<Style x:Key="MyDefaultHyperlinkStyle" TargetType="Hyperlink" BasedOn="{StaticResource {x:Type Hyperlink}}">
<Setter Property="Foreground" Value="Yellow"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="White"/>
</Trigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
<TextBlock>Data import errors</TextBlock>
<ItemsControl ItemsSource="{Binding Messages}"/>
</StackPanel>
</Grid>
</UserControl>
As it stands above, this generates an InvalidOperationException, stating "Can only base on a Style with target type that is base type 'IFrameworkInputElement'."
That can be fixed by putting TargetType="Hyperlink" on the Style definition immediately inside the UserControl.Resources element. However, while the messages are being shown, the link part of them still has the default blue hyperlink styling:
In short, it's not working, so I'd welcome any other suggestions/corrections. :(
Update 2:
Thanks to an alternative solution from Pavlo, it now is working. :)
After some googling I ran into this post: http://www.11011.net/archives/000692.html.
As it described there, it turns out that elements that are not derived from Control (like TextBlock and Hyperlink) do not look for implicit styles outside the DataTemplate boundary.
Again, as the article says, the possible workaround is to specify the style key explicitly. In your case it might be something like this:
<StackPanel Background="Red" TextElement.Foreground="White">
<StackPanel.Resources>
<Style x:Key="MyDefaultHyperlinkStyle" TargetType="Hyperlink" BasedOn="{StaticResource {x:Type Hyperlink}}">
<Setter Property="Foreground" Value="Yellow"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="White"/>
</Trigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
<TextBlock>Data import errors</TextBlock>
<ItemsControl ItemsSource="{Binding Errors}"/>
</StackPanel>
Then, you can add an implicit style for Hyperlink that just references our named style in the DataTemplate resources:
<DataTemplate DataType="{x:Type Importer:ConversionDetailsMessage}">
<DataTemplate.Resources>
<Style TargetType="{x:Type Hyperlink}"
BasedOn="{StaticResource MyDefaultHyperlinkStyle}"/>
</DataTemplate.Resources>
<TextBlock>
<Run Text="{Binding Message, Mode=OneTime}"/>
<Hyperlink Command="Common:ImportDataCommands.ExplainConversionMessage" CommandParameter="{Binding}">
<Run Text="{Binding HelpLink.Item2, Mode=OneTime}"/>
</Hyperlink>
</TextBlock>
</DataTemplate>
And because the data template can be used in different places, there is a possibility that parent container doesn't define a style with key "MyDefaultHyperlinkStyle". In this case an exception will be thrown saying that resource "MyDefaultHyperlinkStyle" cannot be found. To address this, you can define a style with such key that will only inherit default style somewhere in App.xaml:
<Style x:Key="MyDefaultHyperlinkStyle"
BasedOn="{StaticResource {x:Type Hyperlink}}/>
Update:
The code you included in your update will not work because of the nature of static resources, which means that the following resource reference in date template...
BasedOn="{StaticResource MyDefaultHyperlinkStyle}"
... will always point to the following resource (which is the default style):
<Style x:Key="MyDefaultHyperlinkStyle" BasedOn="{StaticResource {x:Type Hyperlink}}"/>
Static resource references are resolved at compile time, therefore the closest resource in the tree is used.
You might be tempted to use DynamicResource, but unfortunately it is not supported with the BasedOn property.
BUT, Foreground property supports dynamic resources, so we can use the same trick with brushes we use inside our style. Here is your test user control modified to use dynamic brushes:
<UserControl x:Class="DataTemplateStyling.StylingView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:DataTemplateStyling="clr-namespace:DataTemplateStyling"
x:Name="root"
Loaded="StylingViewLoaded">
<UserControl.Resources>
<SolidColorBrush x:Key="HyperlinkForeground"
Color="Blue" />
<SolidColorBrush x:Key="HyperlinkHoverForeground"
Color="Gray" />
<Style x:Key="MyDefaultHyperlinkStyle"
TargetType="Hyperlink"
BasedOn="{StaticResource {x:Type Hyperlink}}">
<Setter Property="Foreground"
Value="{DynamicResource HyperlinkForeground}" />
<Style.Triggers>
<Trigger Property="IsMouseOver"
Value="True">
<Setter Property="Foreground"
Value="{DynamicResource HyperlinkHoverForeground}" />
</Trigger>
</Style.Triggers>
</Style>
<DataTemplate DataType="{x:Type DataTemplateStyling:ImportMessage}">
<DataTemplate.Resources>
<Style TargetType="{x:Type Hyperlink}"
BasedOn="{StaticResource MyDefaultHyperlinkStyle}" />
</DataTemplate.Resources>
<TextBlock>
<Run Text="{Binding Message, Mode=OneTime}" />
<Hyperlink NavigateUri="{Binding HelpLink.Item1}">
<Run Text="{Binding HelpLink.Item2, Mode=OneTime}" />
</Hyperlink>
</TextBlock>
</DataTemplate>
</UserControl.Resources>
<Grid DataContext="{Binding ElementName=root}">
<StackPanel Background="Red"
TextElement.Foreground="White">
<StackPanel.Resources>
<SolidColorBrush x:Key="HyperlinkForeground"
Color="Yellow" />
<SolidColorBrush x:Key="HyperlinkHoverForeground"
Color="White" />
</StackPanel.Resources>
<TextBlock>Data import errors</TextBlock>
<ItemsControl ItemsSource="{Binding Messages}" />
</StackPanel>
</Grid>
</UserControl>
It works as expected, i.e. all links inside StackPanel will be Yellow/White, while outside they will be blue.
Hope this helps.

Resources