使用Binding,ReleativeSource触发样式更新

时间:2017-10-15 09:52:38

标签: c# xaml binding uwp-xaml

我正在将RadioButton修改为用作分段控件,从iOS中可以看出。

以下是它的外观: enter image description here

我修改过的Style看起来像这样:

<Style TargetType="control:SegmentRadioButton" x:Key="SegmentedRadioButtonStyle">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="control:SegmentRadioButton">
                <Grid x:Name="RootGrid" Background="{TemplateBinding Background}">
                    <ContentPresenter
                        x:Name="ContentPresenter"
                        Padding="{TemplateBinding Padding}"
                        HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
                        VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
                        AutomationProperties.AccessibilityView="Raw"
                        BorderThickness="{TemplateBinding BorderThickness}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        Content="{TemplateBinding Content}"
                        ContentTemplate="{TemplateBinding ContentTemplate}"
                        ContentTransitions="{TemplateBinding ContentTransitions}" 
                        Background="{TemplateBinding Background}"
                        Foreground="{TemplateBinding Foreground}"/>
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="CommonStates">
                            <VisualState x:Name="Normal">
                                <Storyboard>
                                    <PointerUpThemeAnimation Storyboard.TargetName="RootGrid" />
                                </Storyboard>
                            </VisualState>
                        </VisualStateGroup>
                        <VisualStateGroup x:Name="CheckStates">
                            <VisualState x:Name="Checked">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{Binding SelectedTextColor, RelativeSource={RelativeSource TemplatedParent}}" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{Binding TintColor, RelativeSource={RelativeSource TemplatedParent}}" />
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Unchecked">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{Binding Foreground, RelativeSource={RelativeSource TemplatedParent}}" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{Binding Background, RelativeSource={RelativeSource TemplatedParent}}" />
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Indeterminate" />
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

我的派生控件看起来像这样:

public class SegmentRadioButton : RadioButton
{
    public static readonly DependencyProperty SelectedTextColorProperty = DependencyProperty.Register(
        "SelectedTextColor", 
        typeof(SolidColorBrush), typeof(SegmentRadioButton), 
        new PropertyMetadata(default(SolidColorBrush), new PropertyChangedCallback(OnSelectedTextChanged)));

    public SolidColorBrush SelectedTextColor
    {
        get => (SolidColorBrush) GetValue(SelectedTextColorProperty);
        set => SetValue(SelectedTextColorProperty, value);
    }

    public static readonly DependencyProperty TintColorProperty = DependencyProperty.Register(
        "TintColor", typeof(SolidColorBrush), typeof(SegmentRadioButton), new PropertyMetadata(default(SolidColorBrush), new PropertyChangedCallback(OnTintChanged)));

    public SolidColorBrush TintColor
    {
        get => (SolidColorBrush) GetValue(TintColorProperty);
        set => SetValue(TintColorProperty, value);
    }

    public SegmentRadioButton()
    {

    }

    private static void OnTintChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is SegmentRadioButton segment)
        {
            segment.BorderBrush = (SolidColorBrush) e.NewValue;

            if (segment.IsChecked ?? false)
            {
                // Hack to make the selected segment re-draw.
                segment.IsChecked = false;
                segment.IsChecked = true;
            }
        }
    }

    private static void OnSelectedTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is SegmentRadioButton segment)
        {
            if (segment.IsChecked ?? false)
            {
                // Hack to make the selected segment re-draw.
                segment.IsChecked = false;
                segment.IsChecked = true;
            }
        }
    }

}

问题:这实际上有效,但我想知道是否有更优雅且更少 hacky 的方式来进行TintColorSelectedTextColor更新?从两个内联注释中可以看出,我正在通过翻转IsChecked值来更新/重新绘制

1 个答案:

答案 0 :(得分:1)

我最终使用了Visual State Manager和一个简单的Refresh。

代码在GitHub上:Plugin.SegmentedControl

以下是代码的关键部分:

public class SegmentRadioButton : RadioButton
{
    public static readonly DependencyProperty SelectedTextColorProperty = DependencyProperty.Register(
        "SelectedTextColor", 
        typeof(SolidColorBrush), typeof(SegmentRadioButton), 
        new PropertyMetadata(default(SolidColorBrush), new PropertyChangedCallback(OnSelectedTextChanged)));

    public SolidColorBrush SelectedTextColor
    {
        get => (SolidColorBrush) GetValue(SelectedTextColorProperty);
        set => SetValue(SelectedTextColorProperty, value);
    }

    public static readonly DependencyProperty TintColorProperty = DependencyProperty.Register(
        "TintColor", typeof(SolidColorBrush), typeof(SegmentRadioButton), new PropertyMetadata(default(SolidColorBrush), new PropertyChangedCallback(OnTintChanged)));

    public SolidColorBrush TintColor
    {
        get => (SolidColorBrush) GetValue(TintColorProperty);
        set => SetValue(TintColorProperty, value);
    }

    public static readonly DependencyProperty DisabledColorProperty = DependencyProperty.Register(
        "DisabledColor", typeof(SolidColorBrush), typeof(SegmentRadioButton), new PropertyMetadata(default(SolidColorBrush), new PropertyChangedCallback(OnDisabledColorChanged)));

    public SolidColorBrush DisabledColor
    {
        get => (SolidColorBrush) GetValue(DisabledColorProperty);
        set => SetValue(DisabledColorProperty, value);
    }


    public SegmentRadioButton()
    {
        this.IsEnabledChanged += SegmentRadioButton_IsEnabledChanged;
    }

    private void SegmentRadioButton_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        if (sender is SegmentRadioButton segment)
        {
            Refresh(segment);
        }
    }

    private static void OnTintChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is SegmentRadioButton segment)
        {
            segment.BorderBrush = (SolidColorBrush) e.NewValue;
            Refresh(segment);
        }
    }


    private static void OnDisabledColorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is SegmentRadioButton segment)
        {
            segment.BorderBrush = (SolidColorBrush)e.NewValue;
            Refresh(segment);
        }
    }

    private static void OnSelectedTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is SegmentRadioButton segment)
        {
            Refresh(segment);
        }
    }

    private static void Refresh(SegmentRadioButton segment)
    {
        // Go to "Indeterminate" State to ensure that the GotoState is refreshed even if the state is the same. 
        // Necessary because properties might have changed even when the state have not.

        VisualStateManager.GoToState(segment, "Indeterminate", false);

        if (segment.IsChecked ?? false)
        {
            VisualStateManager.GoToState(segment, segment.IsEnabled ? "Checked" : "DisabledAndChecked", false);
        }
        else
        {
            VisualStateManager.GoToState(segment, segment.IsEnabled ? "Unchecked" : "DisabledAndUnchecked", false);
        }
    }
}

风格:

    <Style TargetType="control1:SegmentRadioButton" x:Key="SegmentedRadioButtonStyle">
    <Setter Property="UseSystemFocusVisuals" Value="True" />
    <Setter Property="VerticalContentAlignment" Value="Center"></Setter>
    <Setter Property="HorizontalContentAlignment" Value="Center"/>
    <Setter Property="Padding" Value="8,4,8,4"></Setter>
    <Setter Property="Background" Value="Transparent"></Setter>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="control1:SegmentRadioButton">
                <Grid x:Name="RootGrid" Background="{TemplateBinding Background}">
                    <ContentPresenter
                        x:Name="ContentPresenter"
                        Padding="{TemplateBinding Padding}"
                        HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
                        VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
                        AutomationProperties.AccessibilityView="Raw"
                        BorderThickness="{TemplateBinding BorderThickness}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        Content="{TemplateBinding Content}"
                        ContentTemplate="{TemplateBinding ContentTemplate}"
                        ContentTransitions="{TemplateBinding ContentTransitions}" 
                        Background="{TemplateBinding Background}"
                        Foreground="{TemplateBinding Foreground}"/>
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="CommonStates">
                            <VisualState x:Name="Normal">
                                <Storyboard>
                                    <PointerUpThemeAnimation Storyboard.TargetName="RootGrid" />
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="DisabledAndChecked">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{Binding DisabledColor, RelativeSource={RelativeSource TemplatedParent}}" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{Binding SelectedTextColor, RelativeSource={RelativeSource TemplatedParent}}" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{Binding DisabledColor, RelativeSource={RelativeSource TemplatedParent}}" />
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="DisabledAndUnchecked">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{Binding DisabledColor, RelativeSource={RelativeSource TemplatedParent}}" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{Binding DisabledColor, RelativeSource={RelativeSource TemplatedParent}}" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{Binding Background, RelativeSource={RelativeSource TemplatedParent}}" />
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                        </VisualStateGroup>
                        <VisualStateGroup x:Name="CheckStates">
                            <VisualState x:Name="Checked">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{Binding TintColor, RelativeSource={RelativeSource TemplatedParent}}" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{Binding SelectedTextColor, RelativeSource={RelativeSource TemplatedParent}}" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{Binding TintColor, RelativeSource={RelativeSource TemplatedParent}}" />
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Unchecked">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{Binding TintColor, RelativeSource={RelativeSource TemplatedParent}}" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{Binding TintColor, RelativeSource={RelativeSource TemplatedParent}}" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{Binding Background, RelativeSource={RelativeSource TemplatedParent}}" />
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>

                            <VisualState x:Name="Indeterminate" />
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>