Style BasedOn attribute failing at run-time - c#

I have a custom button that derives from a Button control. This custom button's (GlyphButton) XAML page declares a style that specifies a control template as follows:
<Button x:Class="Foo.Presentation.Controls.GlyphButton">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="OverridesDefaultStyle"
Value="True" />
<Setter Property="SnapsToDevicePixels"
Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border x:Name="PART_Border"
Padding="1"
Background="Transparent"
BorderThickness="0"
Height="{TemplateBinding Height}"
Width="{TemplateBinding Width}"
BorderBrush="Transparent">
<local:VectorImage x:Name="PART_VectorImage"
Path="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:GlyphButton}}, Path=Path}"
Fill="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:GlyphButton}}, Path=Fill}"
Stretch="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:GlyphButton}}, Path=Stretch}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver"
Value="True">
<Setter TargetName="PART_VectorImage"
Property="Effect">
<Setter.Value>
<DropShadowEffect Color="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:GlyphButton}}, Path=GlowColor, TargetNullValue='Black'}"
ShadowDepth="0"
BlurRadius="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:GlyphButton}}, Path=GlowRadius}"
Opacity="1.0" />
</Setter.Value>
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
I use this control throughout my application. In one instance, I needed to add a context menu with custom styling to show the menu on a left mouse click so I came up with the following:
<controls:GlyphButton Height="24"
Width="24"
Path="{StaticResource HamburgerMenu4}"
Fill="{StaticResource BlueGradientBrush}">
<controls:GlyphButton.Style>
<Style TargetType="{x:Type controls:GlyphButton}" BasedOn="{StaticResource {x:Type controls:GlyphButton}}">
<Style.Triggers>
<EventTrigger RoutedEvent="Click">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="ContextMenu.IsOpen">
<DiscreteBooleanKeyFrame KeyTime="0:0:0"
Value="True" />
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Style.Triggers>
</Style>
</controls:GlyphButton.Style>
At runtime, I get an exception stating that the Xaml parser cannot find a style named 'GlyphButton' when I include the BasedOn attribute shown above. I've discovered a current workaround by declaring the template inline as opposed to using a style that removed the need to base the lowest level style on anything.
<GlypButton>
<GlyphButton.Template>
<!-- Template goes here -->
</GlyphButton.Template>
</GlypButton>
What am I missing here? Am I not following best practices by declaring the custom control's template through a style and if I am, why does this fail?

Related

WPF colorAnimation border brush color throws 'System.Windows.Markup.XamlParseException'

I'm trying to implement color animation on border brush with Style.Triggers.
The ViewModel changes the collection which each Border color is bounded.
I guess the UI elements are not initialized but the condition is fulfilled and then the exception is thrown:
"Additional information: Cannot resolve all property references in the property path 'BorderBrush.Color'. Verify that applicable objects support the properties."
Here the View.cs.xaml code:
<HierarchicalDataTemplate DataType="{x:Type model:SchemaNode}"
ItemsSource="{Binding Children}">
<Border CornerRadius="40">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Background" Value="{DynamicResource MaterialDesignPaper}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Closed}" Value="True">
<Setter Property="Background" Value="{Binding Outline}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Border.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick"
Command="{Binding OnDoubleClick}" CommandParameter="{Binding GlobalId}"/>
</Border.InputBindings>
<Border.ContextMenu>
<ContextMenu ItemsSource="{Binding ContextActions}" Width="150" Placement="Mouse">
<ContextMenu.ItemTemplate>
<DataTemplate DataType="{x:Type helper:ContextAction}">
<MenuItem Header="{Binding Header}" Command="{Binding Command}"/>
</DataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
</Border.ContextMenu>
<Border BorderThickness="3" CornerRadius="360"
BorderBrush="{Binding Outline}" Width="{Binding Width}" Height="{Binding Height}">
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding Located}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard x:Name="LocatedBeginStoryBoard">
<Storyboard Duration="0:0:0.5" RepeatBehavior="Forever" AutoReverse="True">
<ThicknessAnimation Storyboard.TargetProperty="BorderThickness" Duration="0:0:0.4"
FillBehavior="HoldEnd"
To="5"/>
<ThicknessAnimation Storyboard.TargetProperty="BorderThickness"
FillBehavior="HoldEnd"
BeginTime="0:0:0.4"
To="5"/>
<ColorAnimation Storyboard.TargetProperty="BorderBrush.Color"
To="White"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<RemoveStoryboard BeginStoryboardName="LocatedBeginStoryBoard"/>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Image Height="50"
Width="50" Source="{Binding ImageSource, Converter={StaticResource imagePathToImageConverter}}"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Image.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick" Command="{Binding OnDoubleClick}" CommandParameter="{Binding GlobalId}"/>
</Image.InputBindings>
</Image>
</Border>
</Border>
</HierarchicalDataTemplate>
Hopefully I have provided enough code do resolve this problem.
Most likely the BorderBursh you bind via Outline is not a SolidColorBrush but a GradientBrush or simply unassigned and therefore doesn't have a Color property.
You can test this by assigning a SolidColorBrush (directyl or via style).
<Style TargetType="Border">
<Style.Setters>
<Setter Property="BorderBrush" Value="Blue"/>
</Style.Setters>
<!-- etc -->

How to target elements in nested ControlTemplates

In the style for my custom combobox I want to change the Background property of the Border element in the ToggleButton's ControlTemplate when the ComboBox.IsEditable property is set to true but am running into this error targeting the Border:
Cannot find the Trigger target. (The target must appear before any Setters, Triggers, or Conditions that use it.)
Things I've tried:
Targeting the Border by it's name: TargetName="Border"
Targeting the Border as a child of the ToggleButton: TargetName="ToggleButton.Border"
The only information I can find online says:
TargetName operates only within the one ControlTemplate section
But I'm not sure if this is relevant to my situation since the ToggleButtons's ControlTemplate is a child of my custom combobox's ControlTemplate.
Style (irrelevant code removed for brevity)
<Style x:Key="{x:Type local:CustomComboBox}" TargetType="{x:Type local:CustomComboBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomComboBox}">
<Grid>
<ToggleButton x:Name="ToggleButton" Focusable="False" ClickMode="Press"
VerticalContentAlignment="Center"
IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}">
<ToggleButton.Template>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Grid>
<Border x:Name="Border" Grid.ColumnSpan="2"
Background="{Binding Background, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:CustomComboBox}}"/>
</Grid>
</ControlTemplate>
</ToggleButton.Template>
</ToggleButton>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="ComboBox.IsEditable" Value="True">
<Setter Property="Background" TargetName="ToggleButton.Border" Value="Green"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Instead of setting a value on a nested template, do it the other way around, bind the IsEditable property of the parent CustomComboBox in the nested template via a relative source binding. Use a DataTrigger to set the color of the Border to Green in case the property is True.
<Style x:Key="{x:Type local:CustomComboBox}" TargetType="{x:Type local:CustomComboBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomComboBox}">
<Grid>
<ToggleButton x:Name="ToggleButton" Focusable="False" ClickMode="Press"
VerticalContentAlignment="Center"
IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}">
<ToggleButton.Template>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Grid>
<Border x:Name="Border" Grid.ColumnSpan="2"
Background="{Binding Background, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:CustomComboBox}}"/>
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding IsEditable, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:CustomComboBox}}" Value="True">
<Setter TargetName="Border" Property="Background" Value="Green"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</ToggleButton.Template>
</ToggleButton>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

Selecting ListBox items is not firing SelectionChanged event [duplicate]

I have added a DataTemplate to a ListBox class to bind my collection to:
<ListBox x:Name="lstEmails" Height="259" Margin="12,0,12,41" Width="276"
SelectionChanged="lstEmails_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Visibility="Hidden" Content="{Binding ID}"></Label>
<TextBox Width="200" Text="{Binding EmailAddress}"></TextBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This does exactly what I want it to do. Although when I click on the TextBox, the ListBox does not automatically set the associated ListItem as Selected. I could do this in code, but I would prefer to use this as a component (no surprises there then).
Any ideas on how to achieve this?
That doesn't seem to work, it won't let me click on anything. Have I missed something. Here is my new XAML.
<UserControl.Resources>
<!--<TextBox x:Key="TB" x:Name="TextBoxInsideListBoxItemTemplate">
<TextBox.Style>-->
<Style TargetType="{x:Type TextBox}">
<Setter Property="IsHitTestVisible" Value="False" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type ListBoxItem}, AncestorLevel=1}}"
Value="True">
<Setter Property="IsHitTestVisible" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
<!--</TextBox.Style>
</TextBox>-->
</UserControl.Resources>
<Grid>
<ListBox x:Name="lstEmails" Height="259" Margin="12,0,12,41" Width="276" SelectionChanged="lstEmails_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<!--<Label Visibility="Hidden" Content="{Binding ID}"></Label>-->
<TextBox Width="220" Text="{Binding EmailAddress}" >
</TextBox>
<!--<TextBox Width="220" Text="{Binding EmailAddress}" GotFocus="TextBox_GotFocus"></TextBox>-->
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Width="20" Margin="12,0,0,12" Name="btnAdd" VerticalAlignment="Bottom" Click="btnAdd_Click" Height="23" HorizontalAlignment="Left">+</Button>
<Button Width="20" HorizontalAlignment="Left" Margin="30,0,0,12" Name="btnRemove" VerticalAlignment="Bottom" Click="btnRemove_Click" Height="23">-</Button>
<Button Height="23" HorizontalAlignment="Right" Margin="0,0,12,12" Name="btnApply" VerticalAlignment="Bottom" Width="49" Click="btnApply_Click">Apply</Button>
</Grid>
I think the click twice bit is good functionality.
You can trigger on the property IsKeyboardFocusWithin in the ItemContainerStyle and set IsSelected to true.
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsKeyboardFocusWithin, RelativeSource={RelativeSource Self}}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="(ListBoxItem.IsSelected)">
<DiscreteBooleanKeyFrame KeyTime="0" Value="True"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
You could also use a Setter instead of a single frame animation but then the selection will be lost again once the focus leaves the ListBox:
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsKeyboardFocusWithin, RelativeSource={RelativeSource Self}}" Value="True">
<Setter Property="IsSelected" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
If you have multiple instance of ListBox then you may consider using your custom listbox (by deriving it from ListBox). See the explanation here.
Or, use this hack if you have only 1 (or only small number of) such ListBox and don't want to create a separate class for that:
<TextBox x:Name="TextBoxInsideListBoxItemTemplate" ... >
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Setter Property="IsHitTestVisible" Value="False" />
<Style.Triggers>
<DataTrigger
Binding="{Binding IsSelected, RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ListBoxItem}, AncestorLevel=1}}"
Value="True">
<Setter Property="IsHitTestVisible" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
Note that you'll have to click once again to edit text in the TextBox (which is actually cool according to me).
I had a situation where the selection of a listbox item would change its layout, so the control might have moved away from the cursor before the mouse button is released. I have found no better solution than to use a slight delay in the Storyboard if I want to keep everything in xaml.
More importantly, GotKeyboardFocus seems to work better than IsKeyboardFocusWithin for repeated selections.
<EventTrigger RoutedEvent="GotKeyboardFocus">
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="IsSelected">
<DiscreteBooleanKeyFrame KeyTime="00:00:00.3" Value="True"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>

Dock Ability arrows editable

Hi I was wondering is there a way to only edit the Dock Ability Arrows on the Dock Guide for a docking manager using syncfusion for wpf? I can edit the complete style of the docking manager but I only want the ability to edit the arrows as I have the theme in a specific way but need the arrows only to be different is this possible at all? And if so how can I access it?
To do this you need to edit the Drag Provider Templates below is some XAML example for anyone who has a similar problem or was curious as to how to do it
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Syncfusion="http://schemas.syncfusion.com/wpf"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<!-- Control template for TopDragProvider -->
<ControlTemplate x:Key="TopDragProviderTemplate" TargetType="{x:Type ContentControl}">
<Image Name="Img" Width="29" Height="32" Syncfusion:DockPreviewManagerVS2005.ProviderAction="GlobalTop" Source="pack://application:,,,/Syncfusion.Tools.WPF;component/Framework/DockingManager/Resources/DockPreviewVS2005GlobalTop.png" />
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Path=IsSideButtonActive, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Syncfusion:DockPreviewMainButtonVS2005}}}" Value="true">
<Setter TargetName="Img" Property="Source" Value="pack://application:,,,/Syncfusion.Tools.WPF;component/Framework/DockingManager/Resources/DockPreviewVS2005GlobalTopOver.png"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<!-- Control template for LeftDragProvider -->
<ControlTemplate x:Key="LeftDragProviderTemplate" TargetType="{x:Type ContentControl}">
<Image Name="Img" Width="32" Height="29" Syncfusion:DockPreviewManagerVS2005.ProviderAction="GlobalLeft" Source="pack://application:,,,/Syncfusion.Tools.WPF;component/Framework/DockingManager/Resources/DockPreviewVS2005GlobalLeft.png" />
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Path=IsSideButtonActive, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Syncfusion:DockPreviewMainButtonVS2005}}}" Value="true">
<Setter TargetName="Img" Property="Source" Value="pack://application:,,,/Syncfusion.Tools.WPF;component/Framework/DockingManager/Resources/DockPreviewVS2005GlobalLeftOver.png"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<!-- Control template for RightDragProvider -->
<ControlTemplate x:Key="RightDragProviderTemplate" TargetType="{x:Type ContentControl}">
<Image Name="Img" Width="32" Height="29" Syncfusion:DockPreviewManagerVS2005.ProviderAction="GlobalRight" Source="pack://application:,,,/Syncfusion.Tools.WPF;component/Framework/DockingManager/Resources/DockPreviewVS2005GlobalRight.png" />
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Path=IsSideButtonActive, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Syncfusion:DockPreviewMainButtonVS2005}}}" Value="true">
<Setter TargetName="Img" Property="Source" Value="pack://application:,,,/Syncfusion.Tools.WPF;component/Framework/DockingManager/Resources/DockPreviewVS2005GlobalRightOver.png"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<!-- Control template for BottomDragProvider -->
<ControlTemplate x:Key="BottomDragProviderTemplate" TargetType="{x:Type ContentControl}">
<Image Name="Img" Width="29" Height="32" Syncfusion:DockPreviewManagerVS2005.ProviderAction="GlobalBottom" Source="pack://application:,,,/Syncfusion.Tools.WPF;component/Framework/DockingManager/Resources/DockPreviewVS2005GlobalBottom.png" />
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Path=IsSideButtonActive, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Syncfusion:DockPreviewMainButtonVS2005}}}" Value="true">
<Setter TargetName="Img" Property="Source" Value="pack://application:,,,/Syncfusion.Tools.WPF;component/Framework/DockingManager/Resources/DockPreviewVS2005GlobalBottomOver.png"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>

Changing the background of a button in a DataGrid if the row is selected - WPF

I have a datagrid which contains a template column which holds a few buttons.
I need the color of these buttons to change from black to white when the row is selected.
Although I am not sure how can I reach "DataGridRow.IsSelected" from the buttons setters.
Here is what I tried and didn't work:
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button CommandParameter="{Binding RelativeSource={RelativeSource Mode=Self}, Path=DataContext}"
Command="{Binding ViewModel.OnRemoveDirectoryClick, ElementName=Root}">
<Button.Style>
<Style>
<Setter Property="Button.Background">
<Setter.Value>
<ImageBrush ImageSource="../../Images/menu_delete.png"/>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="DataGridRow.IsSelected" Value="True">
<Setter Property="Button.Background">
<Setter.Value>
<ImageBrush ImageSource="../../Images/menu_delete_white.png"/>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Thanks ahead,
Yotam
Eventually I found that I needed to use DatatTrigger with a relative source to DataGridRow
<Button.Style>
<Style>
<Setter Property="Button.Background">
<Setter.Value>
<ImageBrush ImageSource="../../Images/menu_delete.png"/>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsSelected, RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}}}" Value="True">
<Setter Property="Button.Background">
<Setter.Value>
<ImageBrush ImageSource="../../Images/menu_delete_white.png"/>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
An additional and perhaps more elegant way I found to tackle this problem was using a template which contains rectangle with white filling which takes the button's background as the opacity mask.
<ControlTemplate x:Key="DataGridButtonTemplate" TargetType="{x:Type Button}">
<Grid Style="{x:Null}">
<Rectangle x:Name="Mask" Width="{TemplateBinding Width}" Height="{TemplateBinding Height}" Opacity="{StaticResource NormalOpacity}" Style="{x:Null}"
OpacityMask="{TemplateBinding Background}" Fill="White" Visibility="{Binding Path=IsSelected, RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}}, Converter={StaticResource BoolToVisibilityConverter}}"/>
</Grid>
</ControlTemplate>
Try putting this style as Resource
<Style TargetType="{x:Type DataGridCell}">
<Style.Triggers>
<Trigger Property="DataGridCell.IsSelected"
Value="True">
<Setter Property="Foreground" TargetName="YourButtonName"
Value="YourColor" />
</Trigger>
</Style.Triggers>
</Style>

Categories

Resources