Change background colour of a ComboBox in WPF without pasting entire template? - wpf

I understand you can right click, Edit Template > Edit a Copy, paste the entire ComboBox template then change a few lines, but is there really not a way to change the background in just a few lines of code?
I was able to achieve it with this code, but that eliminates the dropdown arrow/menu which basically makes it useless.
<Style TargetType="{x:Type ComboBox}">
<Setter Property="Background" Value="Red" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ComboBox}">
<Border Background="{TemplateBinding Background}">
<ContentPresenter></ContentPresenter>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

I am afraid you cannot "override" only a part of a ControlTemplate and given the way the default ControlTemplate of the ComboBox is defined, you cannot simply set or property or create a derived style with a trigger to change the background colour of it. This is explained in detail here.
What you can do is to change the background of the loaded template element at runtime programmtically:
private void ComboBox_Loaded(object sender, RoutedEventArgs e)
{
ComboBox comboBox = (ComboBox)sender;
ToggleButton toggleButton = comboBox.Template.FindName("toggleButton", comboBox) as ToggleButton;
if (toggleButton != null)
{
Border border = toggleButton.Template.FindName("templateRoot", toggleButton) as Border;
if (border != null)
border.Background = Brushes.Yellow;
}
}
XAML:
<ComboBox Loaded="ComboBox_Loaded">
<ComboBoxItem Background="Yellow">1</ComboBoxItem>
<ComboBoxItem Background="Yellow">2</ComboBoxItem>
<ComboBoxItem Background="Yellow">3</ComboBoxItem>
</ComboBox>
The other option is to copy the entire template into your XAML markup and edit it as per your requirements.

Related

keep tooltip opened if a property is set to true until user clicks anywhere with the mouse

I have an WPF tooltip defined within a dictionary.xaml that I want to apply to all my items, labels, buttons, etc. I import this dictionary in my view.
<Style x:Key="{x:Type ToolTip}" TargetType="ToolTip">
<Setter Property="OverridesDefaultStyle" Value="true" />
<Setter Property="HorizontalOffset" Value="1" />
<Setter Property="VerticalOffset" Value="1" />
<Setter Property="Background" Value="White" />
<Setter Property="Foreground" Value="Black" />
<Setter Property="FontSize" Value="12" />
<Setter Property="FontFamily" Value="Segoe UI" />
<Setter Property="DataContext" Value="{Binding Path=PlacementTarget.DataContext, RelativeSource={x:Static RelativeSource.Self}}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToolTip">
<Canvas Width="225" Height="131">
<Path x:Name="Container"
Canvas.Left="0"
Canvas.Top="0"
Margin="0"
Data="M8,7.41 L15.415,0 L22.83,7.41 L224,7.41 L224,130 L0,130 L0,7.41 L8,7.41"
Fill="{TemplateBinding Background}"
Stroke="Gray">
<Path.Effect>
<DropShadowEffect BlurRadius="10"
Opacity="0.5"
ShadowDepth="4" />
</Path.Effect>
</Path>
<TextBlock Canvas.Left="10"
Canvas.Top="10"
Width="100"
Height="65"
Text="{TemplateBinding Content}"
TextWrapping="WrapWithOverflow" />
</Canvas>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
so I can use it as below:
<Label x:Name="myLabel"
ToolTip="This is a custom tooltip for my label."
ToolTipClosing="tt_ToolTipClosing"/>
also I have other items:
<Button x:Name="myBtn"
Tooltip="This is a custom tooltip for my button"
ToolTipClosing="tt_ToolTipClosing"/>
Tooltip is working fine, when I hover the mouse on it, the tooltip is shown.
Now I am trying to do below 2 things:
Implement a tooltip closing event handler that works for all my items (labels, buttons, etc.)
Keep the tooltip always opened until user clicks on anywhere in the screen with the mouse if a property keepOpened is set to true. Otherwise, if keepOpened is set to false, it behaves as a normal tooltip, it closes automatically when mouse leaves item.
In the code behind now I have below event handler (I have got this code from here):
private void tt_ToolTipClosing(object sender, ToolTipEventArgs e)
{
if (keepOpened)
{
// TODO: Determine who is the sender, a label, a button, etc.
// We suppose in this example it is a label
Label myLabel = sender as Label;
ToolTip tt = myLabel.ToolTip as ToolTip;
if (tt.PlacementTarget == null)
{
tt.PlacementTarget = myLabel;
}
tt.IsOpen = true;
}
}
For first point, I guess I need to use a switch to determine if sender is a button, label, etc. and then get the tooltip by casting it. I do not know if there are any other better way to do it. If so, please tell me.
For second point, KeepOpened would be a property defined in the view model, so I guess I need to use a command instead and move the above event to the view model, right? I know that I can use a popup instead but i don't want to use it as it is always on top of all desktop objects - even if you switch to another program, the popup will be visible and obscure part of the other program.
Also, now I have a problem in above event handler when casting to a Tooltip, tt is getting null. Why? I have set the datacontext in the style that is defined in the dictionary.xaml.
If you want to apply this style as a generic style for multiple controls, you should remove the x:Key part which should be just a string anyway. Change this:
<Style x:Key="{x:Type ToolTip}" TargetType="ToolTip">
to:
<Style TargetType="{x:Type ToolTip}">
Also, I would get rid of that Canvas with fixed size. Use a Border and put a Grid inside of it where you put the Path and the TextBlock. Leave the tooltip size itself to the content. This Canvas Width="225" Height="131" doesn't look nice.
For point 2, to make the tooltip to not close automatically after 5 seconds, but only when the user moves the mouse pointer away from the control, you can add this in your code:
ToolTipService.ShowDurationProperty.OverrideMetadata(typeof(DependencyObject), new FrameworkPropertyMetadata(Int32.MaxValue));
Make sure you call this code only once, for example in App.xaml.cs.
Now your tooltips should stay open several weeks if the user keeps the mouse pointer over the control that displays the tooltip, allowing the user to read the content of it.
I guess I need to use a switch to determine if sender is a button, label, etc. and then get the tooltip by casting it. I do not know if there are any other better way to do it. If so, please tell me.
You could cast the sender argument to a FrameworkElement and then access the ToolTip property:
private void tt_ToolTipClosing(object sender, ToolTipEventArgs e)
{
FrameworkElement fe = (FrameworkElement)sender;
switch (fe.ToolTip)
{
case string s:
//do something with the string s
break;
case ToolTip tt:
//do something with the ToolTip tt
break;
}
}

Overwrite the style of ComboBoxItem in ComboBox style

I have a Silverlight project and I want to customize the appearance of ComboBox control, so I add a ResourceDictionary.xaml file, overwrite the default style of ComboBox, apply this new style to ComboBox, and it works fine.
<Style TargetType="ComboBox" x:Key="CommonComboBoxStyle">
<Setter Property = "xxx" Value="XXX" />
....
</Style>
Soon I realized that I also need to customize the appearance of ComboBoxItem, I want to change its background color when an item is selected/mouseovered, so I overwritten its default style:
<Style x:Key="FilterDownComboBoxItemContainerStyle" TargetType="ComboBoxItem">
.....
<Rectangle x:Name="fillColor" Fill="#FF0054A6" IsHitTestVisible="False" Opacity="0" RadiusY="0" RadiusX="0"/>
<Rectangle x:Name="fillColor2" Fill="#FF0054A6" IsHitTestVisible="False" Opacity="0" RadiusY="0" RadiusX="0"/>
</Style>
Here is the question, I want to put FilterDownComboBoxItemContainerStyle in CommonComboBoxStyle, so I just need to apply CommonComboBoxStyle to ComboBoxes that I want, no need to apply FilterDownComboBoxItemContainerStyle separately to every ComboBoxItem, are there any ways to set style of ComboBoxItem in ComboBox style?
are there any ways to set style of ComboBoxItem in ComboBox style?
Yes, you can use ComboBox.ItemContainerStyle for this:
<Style TargetType="ComboBox" x:Key="CommonComboBoxStyle">
<Setter Property="ItemContainerStyle" Value="{StaticResource FilterDownComboBoxItemContainerStyle}" />
</Style>

ListViewitem background color not changing on selection

I have a ListView...and I am overriding the template of listview item to add a border to it.
But, when I do that - while selecting an item in listview ...the background is not getting changed to blue(as it do normally).
Do I need to add TemplateBinding for background color ? Please help me.
<ListView.Resources>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType='{x:Type ListViewItem}'>
<Border DataContext="{Binding Item.Type}" Style="{StaticResource ValidationResultBorderStyle}" HorizontalAlignment="Left"
<GridViewRowPresenter />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.Resources>
When we override the Template of any item containers, we should maintain their behaviors from their default template... To do that we must maintain their content presenters and triggers etc...
These articles may help you...
http://social.msdn.microsoft.com/forums/en-US/wpf/thread/8d849ee7-a502-445e-bb77-aa00a2e59982
Listview selection color

How to use IsKeyboardFocusWithin and IsSelected together?

I have a style defined for my ListBoxItems with a trigger to set a background color when IsSelected is True:
<Style x:Key="StepItemStyle" TargetType="{x:Type ListBoxItem}">
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border Name="Border" Padding="0" SnapsToDevicePixels="true">
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="Border" Property="Background" Value="#40a0f5ff"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
This style maintains the selected item even when the ListBox and ListBoxItem loses focus, which in my case is an absolute must.
The problem is that I also want the ListBoxItem to be selected when one of its TextBox's child gets focused. To achieve this I add a trigger that sets IsSelected to true when IsKeyboardFocusWithin is true:
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="IsSelected" Value="True" />
</Trigger>
When I add this trigger the Item is selected when the focus is on a child TextBox, but the first behaviour disappears. Now when I click outside the ListBox, the item is de-selected.
How can I keep both behaviours?
When your listbox looses focus, it will set selected item to null because of your trigger. You can select on focus using some code behind that will not unselect when you loose focus.
XAML:
<Window x:Class="SelectedTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300">
<StackPanel>
<TextBox Text="Loose focus here" />
<ListBox Name="_listBox" ItemsSource="{Binding Path=Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" GotFocus="OnChildGotFocus">
<TextBox Text="{Binding .}" Margin="10" />
<TextBox Text="{Binding .}" Margin="10" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border Name="Border" SnapsToDevicePixels="true" Background="Transparent">
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="Border" Property="Background" Value="Red"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</StackPanel>
</Window>
Code behind:
private void OnChildGotFocus(object sender, RoutedEventArgs e)
{
_listBox.SelectedItem = (sender as StackPanel).DataContext;
}
"When I add this trigger the Item is selected when the focus is on a child TextBox, but the first behaviour disappears. Now when I click outside the ListBox, the item is de-selected."
Actually, I don't think it has lost that original behavior. What I suspect is happening is you're clicking directly in the textbox from somewhere else so the underlying ListBoxItem never actually became selected. If it did however, you'd see the selection would still remain after you left as you want.
You can test this by forcing the ListBoxItem to be selected by clicking directly on it (side-note: you should always give it a background, even if just 'transparent' so it can receive mouse clicks, which it won't if it's null) or even just hitting 'Shift-Tab' to set the focus there, back from the textbox.
However, that doesn't solve your issue, which is that the TextBox gets the focus but doesn't let the underlying ListBoxItem know about it.
The two approaches you can use for that are an event trigger or an attached behavior.
The first is an event trigger on the IsKeyboardFocusWithinChanged event where you set 'IsSelected' to true if the keyboard focus changed to true. (Note: Sheridan's answer does a faux-change-notification but it should not be used in cases where you can multi-select in the list because everything becomes selected.) But even an event trigger causes issues because you lose the multi-select behaviors such as toggling or range-clicking, etc.
The other (and my preferred approach) is to write an attached behavior which you set on the ListBoxItem, either directly, or via a style if you prefer.
Here's the attached behavior. Note: You again would need to handle the multi-select stuff if you want to implement that. Also note that although I'm attaching the behavior to a ListBoxItem, inside I cast to UIElement. This way you can also use it in ComboBoxItem, TreeViewItem, etc. Basically any ContainerItem in a Selector-based control.
public class AutoSelectWhenAnyChildGetsFocus
{
public static readonly DependencyProperty EnabledProperty = DependencyProperty.RegisterAttached(
"Enabled",
typeof(bool),
typeof(AutoSelectWhenAnyChildGetsFocus),
new UIPropertyMetadata(false, Enabled_Changed));
public static bool GetEnabled(DependencyObject obj){ return (bool)obj.GetValue(EnabledProperty); }
public static void SetEnabled(DependencyObject obj, bool value){ obj.SetValue(EnabledProperty, value); }
private static void Enabled_Changed(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var attachEvents = (bool)e.NewValue;
var targetUiElement = (UIElement)sender;
if(attachEvents)
targetUiElement.IsKeyboardFocusWithinChanged += TargetUiElement_IsKeyboardFocusWithinChanged;
else
targetUiElement.IsKeyboardFocusWithinChanged -= TargetUiElement_IsKeyboardFocusWithinChanged;
}
static void TargetUiElement_IsKeyboardFocusWithinChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var targetUiElement = (UIElement)sender;
if(targetUiElement.IsKeyboardFocusWithin)
Selector.SetIsSelected(targetUiElement, true);
}
}
...and you simply add this as a property setter in your ListBoxItem's style
<Setter Property="behaviors:AutoSelectWhenAnyChildGetsFocus.Enabled" Value="True" />
This of course assumes you've imported an XML namespace called 'behaviors' that points to the namespace where the class is contained. You can put the class itself in a shared 'Helper' library, which is what we do. That way, everywhere we want it, its a simple property set in the XAML and the behavior takes care of everything else.
I figured out that IsKeyboardFocusWithin is not the best solution.
What I did in this case was to set the style on all of the controls used as DataTemplate to send the GotFocus-event to be handled in code behind. Then, in code behind, I searched up the visual tree (using VisualTreeHelper) to find the ListViewItem and set IsSelected to true. This way it does not "touch" the DataContext and works just with the View elements.
<Style TargetType="{x:Type Control}" x:Key="GridCellControlStyle">
...
<EventSetter Event="GotFocus" Handler="SelectListViewItemOnControlGotFocus"/>
...
private void SelectListViewItemOnControlGotFocus(object sender, RoutedEventArgs e)
{
var control = (Control)sender;
FocusParentListViewItem(control);
}
private void FocusParentListViewItem(Control control)
{
var listViewItem = FindVisualParent<ListViewItem>(control);
if (listViewItem != null)
listViewItem.IsSelected = true;
}
public static T FindVisualParent<T>(UIElement element) where T : UIElement
{
UIElement parent = element;
while (parent != null)
{
var correctlyTyped = parent as T;
if (correctlyTyped != null)
{
return correctlyTyped;
}
parent = VisualTreeHelper.GetParent(parent) as UIElement;
}
return null;
}

Underline the implicit Textblock created in Silverlight for a ContentPresenter when Content is a string?

I am trying to create a template for a content control such as Button or HeaderedContentControl etc. where the text is underlined.
I just want to underline the text when Content="This text is underlined" is specified.
It must continue to work as normal if Content is another UIElement.
Most posts asking this same question are satisfied with modifying the template to only work for a string as content. Scott Gu has a good article about styling buttons but doesn't address this issue.
The following sample will work if you actually pass in Content as an instance of type TextBlock but not as a string. Surely the visual tree has a TextBlock so it should style it. Perhaps this is a Sivlerlight limitation.
This example shows black text and big red text when I want it to display both as big red text.
<navigation:Page.Resources>
<Style TargetType="TextBlock" x:Key="style123">
<Setter Property="Foreground" Value="Red"/>
<Setter Property="FontSize" Value="72"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="TextDecorations" Value="Underline"/>
</Style>
</navigation:Page.Resources>
<StackPanel>
<!-- This doesn't work and shows black text -->
<ContentPresenter Content="Small black text">
<ContentPresenter.Resources>
<Style TargetType="TextBlock" BasedOn="{StaticResource style123}"/>
</ContentPresenter.Resources>
</ContentPresenter>
<!-- This works and shows red text -->
<ContentPresenter>
<ContentPresenter.Content>
<TextBlock Text="This is big red text"/>
</ContentPresenter.Content>
<ContentPresenter.Resources>
<Style TargetType="TextBlock" BasedOn="{StaticResource style123}"/>
</ContentPresenter.Resources>
</ContentPresenter>
</StackPanel>
You could subclass whatever actual ContentControl (i.e. Button) you are using and override OnContentChanged in order to reset the Content property to an underlined TextBlock if the newContent is a string. In the case that the newContent is not a string it would perform in the usual way.
public class UnderlineButton : Button
{
protected override void OnContentChanged(object oldContent, object newContent)
{
if (newContent is string)
{
TextBlock textBlock = new TextBlock();
textBlock.Text = newContent as string;
textBlock.TextDecorations = TextDecorations.Underline;
this.Content = textBlock;
}
base.OnContentChanged(oldContent, newContent);
}
}
It's kind of annoying to subclass just to accomplish this but it avoids messy style templates and subclassing ContentPresenter.
Try this example, using a DataTemplate to custom-render string content (I've just set the background to red):
<ContentControl Content="{Binding YourData}" >
<ContentControl.Resources>
<DataTemplate DataType="{x:Type s:String}">
<TextBlock Text="{Binding}" Background="Red" />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
EDIT: just as a note, you could pull this out into a ContentControl style rather than applying it inline each time, if you need better reusability...

Resources