0

I am designing a WPF application where I want to give a marker(e.g. Red Border) to the default button of a window. The behaviour of the marker should be

  • When no button is having the focus then the marker should be visible on the default button
  • If the default button gets the focus, then also the marker should be visible
  • if any other button gets the focus then the marker should be hidden

As I am using the materialDesign, so I had to extend the "MaterialDesignRaisedButton" style. I have written one converter which will check all the buttons present in the window and set the marker on the default button as per my requirement.

internal class ButtonDefaultPropertyConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        bool nonDefaultFocus = false;
        foreach (Button button in FindVisualChildren<Button>((DependencyObject)value))
        {                
            if (button.IsFocused && !button.IsDefault)
            {
                nonDefaultFocus = true;
                break;
            }
        }
        foreach (Button button in FindVisualChildren<Button>((DependencyObject)value))
        {
            if (button.IsDefault)
            {
                if (!nonDefaultFocus)
                    button.BorderBrush = Brushes.Red;
                else
                {
                    button.BorderBrush = (Brush)Application.Current.Resources["PrimaryHueMidBrush"];
                }
            }
            else
            {
                button.BorderBrush = (Brush)Application.Current.Resources["PrimaryHueMidBrush"];
            }
        }
        return 1;
    }

I have written the trigger on a different property, just to call the converter. I have it in the APP.xaml so that I can use it through out my application. The xaml looks like this

<Style x:Key="MyButton" TargetType="{x:Type Button}">
        <Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual}"/>
        <Setter Property="Background" Value="{StaticResource PrimaryHueMidBrush}"/>
        <Setter Property="BorderBrush" Value="{StaticResource PrimaryHueMidBrush}"/>
        <Setter Property="Foreground" Value="{StaticResource PrimaryHueLightForegroundBrush}"/>
        <Setter Property="materialDesign:ButtonProgressAssist.IndicatorForeground" Value="{DynamicResource PrimaryHueMidForegroundBrush}" />
        <Setter Property="materialDesign:ButtonProgressAssist.IndicatorBackground" Value="{StaticResource PrimaryHueMidBrush}" />
        <Setter Property="materialDesign:RippleAssist.Feedback" Value="White" />
        <Setter Property="Cursor" Value="Hand"/>
        <Setter Property="materialDesign:ShadowAssist.ShadowDepth" Value="Depth1" />
        <Setter Property="TextBlock.FontWeight" Value="Medium"/>
        <Setter Property="TextBlock.FontSize" Value="14"/>
        <Setter Property="BorderThickness" Value="1"/>
        <Setter Property="HorizontalContentAlignment" Value="Center"/>
        <Setter Property="VerticalContentAlignment" Value="Center"/>
        <Setter Property="Padding" Value="16 4 16 4"/>
        <Setter Property="Height" Value="32" />
        <Setter Property="materialDesign:ButtonProgressAssist.IsIndicatorVisible" Value="False" />
        <Setter Property="materialDesign:ButtonProgressAssist.Opacity" Value=".4" />
        <Setter Property="materialDesign:ButtonAssist.CornerRadius" Value="2" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Button}">
                    <Grid>
                        <AdornerDecorator CacheMode="{Binding RelativeSource={RelativeSource Self}, Path=(materialDesign:ShadowAssist.CacheMode)}">
                            <Grid>
                                <Border Background="{TemplateBinding Background}" 
                                    CornerRadius="{Binding Path=(materialDesign:ButtonAssist.CornerRadius), RelativeSource={RelativeSource TemplatedParent}}"
                                    BorderThickness="{TemplateBinding BorderThickness}"
                                    BorderBrush="{TemplateBinding BorderBrush}"
                                    x:Name="border" 
                                    Effect="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(materialDesign:ShadowAssist.ShadowDepth), Converter={StaticResource ShadowConverter}}"/>
                                <ProgressBar x:Name="ProgressBar"
                                         Style="{DynamicResource MaterialDesignLinearProgressBar}"
                                         Minimum="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(materialDesign:ButtonProgressAssist.Minimum)}"
                                         Maximum="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(materialDesign:ButtonProgressAssist.Maximum)}"
                                         Foreground="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(materialDesign:ButtonProgressAssist.IndicatorForeground)}"
                                         Background="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(materialDesign:ButtonProgressAssist.IndicatorBackground)}"
                                         Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(materialDesign:ButtonProgressAssist.Value)}"
                                         IsIndeterminate="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(materialDesign:ButtonProgressAssist.IsIndeterminate)}"
                                         Visibility="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(materialDesign:ButtonProgressAssist.IsIndicatorVisible), Converter={StaticResource BooleanToVisibilityConverter}}"
                                         Height="{TemplateBinding Height}"
                                         Width="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ButtonBase}}, Path=ActualWidth}"
                                         Opacity="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(materialDesign:ButtonProgressAssist.Opacity)}"
                                         HorizontalAlignment="Left"
                                         VerticalAlignment="Center">
                                </ProgressBar>
                            </Grid>
                        </AdornerDecorator>
                        <materialDesign:Ripple Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" Focusable="False"
                                ContentStringFormat="{TemplateBinding ContentStringFormat}"
                                HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
                                VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
                                Padding="{TemplateBinding Padding}"
                                SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}">
                            <materialDesign:Ripple.Clip>
                                <MultiBinding Converter="{StaticResource BorderClipConverter}">
                                    <Binding ElementName="border" Path="ActualWidth" />
                                    <Binding ElementName="border" Path="ActualHeight" />
                                    <Binding ElementName="border" Path="CornerRadius" />
                                    <Binding ElementName="border" Path="BorderThickness" />
                                </MultiBinding>
                            </materialDesign:Ripple.Clip>
                        </materialDesign:Ripple>
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsDefault" Value="True">
                            <Setter Property="BorderThickness" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}},Converter={StaticResource ButtonDefaultPropertyConverter}}" />
                        </Trigger>
                        <Trigger Property="IsMouseOver" Value="true">
                            <Setter TargetName="border" Property="materialDesign:ShadowAssist.Darken" Value="True" />
                        </Trigger>
                        <Trigger Property="IsKeyboardFocused" Value="true">
                            <Setter TargetName="border" Property="materialDesign:ShadowAssist.Darken" Value="True" />
                            <Setter Property="BorderThickness" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Converter={StaticResource ButtonDefaultPropertyConverter}, UpdateSourceTrigger=Explicit}" />
                        </Trigger>
                        <Trigger Property="IsEnabled" Value="false">
                            <Setter Property="Opacity" Value="0.23"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

And the button simply looks like this

<Button Style="{StaticResource MyButton}" Grid.Row="0" x:Name="btn1" Height="40" Width="100" Content="Button1" IsDefault="True" />
<Button Style="{StaticResource MyButton}" Grid.Row="1" x:Name="btn2" Height="40" Width="100" Content="Button2" />

Although this almost serves my purpose. But the only problem is when the focus is lost from a button then the converter is not being triggered. I have googled it and found that I can use EventTriggers for LostFocus event. The problem with EventTrigger is that I can not use bindings in the EventTriggers.

So now I am stuck.

Would be really nice if somebody can help...

Thanks in advance

Joy
  • 1,609
  • 3
  • 16
  • 28
  • Why don't you simply add an `EventSetter` to the `Style` and handle the `LostFocus` event programmatically? โ€“ mm8 Jun 24 '20 at 16:08
  • Thanks @mm8 . I used EventSetter and it solved my problem...:) :) โ€“ Joy Jun 24 '20 at 17:51

2 Answers2

0

The converter ButtonDefaultPropertyConverter can only process the first two conditions correctly. He will not always be able to cope with the third condition. This is correct only if the window focus moves from button to button and there are no other elements with focus.

For normal operation, the converter must at least transmit the focus values โ€‹โ€‹of all other buttons. It seems to me that it will be easier to implement with an Attached Property.

EldHasp
  • 6,079
  • 2
  • 9
  • 24
0

You could simply add an EventSetter to the Style and handle the LostFocus event programmatically.

If the Style is defined in a ResourceDictionary, you should add a code-behind class to it and define the event handler in this one.

mm8
  • 163,881
  • 10
  • 57
  • 88