WPF DependencyProperty ... теперь это работает ... сейчас нет ... как получилось? - PullRequest
1 голос
/ 29 января 2011

У меня есть ClockFace UserControl, который предоставляет ряд свойств, позволяющих пользователям стилизовать его. Часы имеют два объекта Ellipse в качестве границ; внешняя граница и внутренняя граница.

<Ellipse Name="OuterBorder" Panel.ZIndex="5" StrokeThickness="{Binding BorderOuterThickness}" Stroke="{Binding BorderOuteBrush}" />
<Ellipse Name="InnerBorder" Panel.ZIndex="6" StrokeThickness="{Binding BorderInnerThickness}" Margin="{Binding StrokeThickness, ElementName=OuterBorder}" Stroke="{Binding BorderInnerBrush}">

public static readonly DependencyProperty BorderInnerBrushProperty = DependencyProperty.Register("BorderInnerBrush", typeof(Brush), typeof(ClockFace), new 

PropertyMetadata(new LinearGradientBrush(Color.FromRgb(118, 57, 57), Color.FromRgb(226, 185, 185), new Point(0.5, 0), new Point(0.5, 1))));

public Brush BorderInnerBrush
{
    get { return (Brush)GetValue(BorderInnerBrushProperty); }
    set { SetValue(BorderInnerBrushProperty, value); }
}

public static readonly DependencyProperty BorderOuterBrushProperty = DependencyProperty.Register("BorderOuterBrush", typeof(Brush), typeof(ClockFace), new 

PropertyMetadata(new LinearGradientBrush(Color.FromRgb(226, 185, 185), Color.FromRgb(118, 57, 57), new Point(0.5, 0), new Point(0.5, 1))));

public Brush BorderOuterBrush
{
    get { return (Brush)GetValue(BorderOuterBrushProperty); }
    set { SetValue(BorderOuterBrushProperty, value); }
}

Они могут быть установлены в стиле и будут корректно обновляться при переключении стилей. Я подумал, что был бы умен и добавил бы свойство ярлыка BorderBrush, которое передает себя в свойство BorderOuterBrush, а затем передает свою копию с обратным градиентом в свойство BorderInnerBrush. Чтобы разрешить запуск этого кода, когда свойство было установлено после инициализации (путем переключения стилей), мне пришлось реализовать метод PropertyChangedCallback, который вызывает метод SetBorderBrushes.

public new static readonly DependencyProperty BorderBrushProperty = DependencyProperty.Register("BorderBrush", typeof(Brush), typeof(ClockFace), new 

PropertyMetadata(OnBorderBrushChanged));

private static void OnBorderBrushChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
    ((ClockFace)dependencyObject).SetBorderBrushes((Brush)e.NewValue);
}

public new Brush BorderBrush
{
    get { return (Brush)GetValue(BorderBrushProperty); }
    set { SetValue(BorderBrushProperty, value); }
}

private void SetBorderBrushes(Brush brush)
{
    if (brush != null)
    {
        BorderOuterBrush = brush;
        Brush innerBrush = BorderOuterBrush.Clone();
        if (brush.GetType() == typeof(LinearGradientBrush) || brush.GetType() == typeof(RadialGradientBrush))
        {
            foreach (GradientStop gradientStop in ((GradientBrush)innerBrush).GradientStops)
            {
                gradientStop.Offset = 1 - gradientStop.Offset;
            }
        }
        BorderInnerBrush = innerBrush;
    }
}

Когда свойство BorderBrush установлено в стиле, все работает нормально ... пока я не переключу стиль во время выполнения. Теперь происходит самое странное ... позвольте мне объяснить.

У меня есть четыре предустановленных стиля, и по отдельности они все прекрасно работают. Три из них используют два свойства BorderInnerBrush и BorderOuterBrush, а одно - свойство ярлыка BorderBrush. Я могу переключаться между стилями с помощью ContextMenu и некоторого кода, который обращается к стилям xaml из ресурсов и устанавливает их для свойства Style объекта ClockFace.

Я могу без проблем переключаться между тремя стилями, которые не используют свойство ярлыка бесконечно. Я также могу переключиться на стиль, который использует свойство ярлыка, и он выглядит просто отлично. Это когда начинается странность.

После переключения на стиль, использующий свойство BorderBrush, свойства BorderInnerBrush и BorderOuterBrush просто перестают работать. Предустановленные объекты Brush, установленные в различных стилях, больше не устанавливаются для двух объектов Ellipse. Я подключил некоторые методы PropertyChangedCallback к свойствам внутренней и внешней границы, чтобы увидеть, что происходит.

Когда я впервые запускаю приложение, я могу переключиться на три стиля, которые без проблем используют свойство ярлыка. Я установил точки останова для всех трех методов PropertyChangedCallback свойства Brush и отладил программу. При переключении на каждый из этих трех стилей точки прерывания «методы обратного вызова» свойств внутренней и внешней границы были достигнуты, как и следовало ожидать. При переключении на стиль, который использует свойство BorderBrush, была достигнута точка останова его метода обратного вызова. Метод SetBorderBrushes устанавливает два других объекта Brush для граничных объектов таким образом, чтобы точки прерывания «методов обратного вызова» свойств внутренней и внешней границ были затем достигнуты, как и следовало ожидать.

Опять же, это странная часть. При переключении на любой из трех других стилей после этого точки останова свойств внутренней и внешней границы «методов обратного вызова» больше не затрагиваются вообще. Вместо этого достигается точка останова в методе обратного вызова, присоединенном к свойству BorderBrush, и значение e.NewValue равно нулю. Поскольку нулевые значения игнорируются в методе SetBorderBrushes, дальнейшие точки останова не выполняются.

После дальнейшего изучения я обнаружил, что значение e.NewValue было нулевым, поскольку в BorderBrushProperty DependencyProperty не было установлено значение по умолчанию. Действительно, после добавления объекта Brush по умолчанию в объявление это Brush, который будет передан в e.NewValue в методе обратного вызова. Хотя точки останова в методах обратного вызова двух свойств внутренней и внешней границы будут попадать после этого, это было только потому, что они установлены в методе SetBorderBrushes. Объекты Brush, установленные в объектах BorderInnerBrush и BorderOuterBrush в стилях, никогда не передаются этим свойствам после того, как свойство BorderBrush было использовано один раз.

И последнее замечание: как значение по умолчанию для свойства BorderBrushProperty устанавливается, когда значение не задано явно в стиле, так и значения по умолчанию для объектов DependencyProperty для внутренней и внешней границы также устанавливаются, когда их значенияне устанавливается явно в стиле, но только когда свойство BorderBrush также не установлено в стиле.

Я застрял на этом уже несколько дней, и хотя простым решением было бы удалить свойство ярлыка, я бы лучше выяснил, что происходит, и исправил его.Я надеюсь, что предоставил достаточно информации, чтобы какая-то яркая искра смогла решить эту проблему, поэтому, если у вас есть идеи или вопросы, поделитесь ими.Большое спасибо.

ОБНОВЛЕНИЕ >>

Следуя совету Рика, я создал еще одну пару свойств Brush и установил их из методов обратного вызова, прикрепленных к другим трем свойствам Brush.Используя эту настройку, я мог переключаться между всеми стилями без каких-либо визуальных проблем ... или так я думал.

Теперь я могу переключиться со стиля, который использует свойство BorderBrush (ярлык), на стили, которые устанавливаютдва свойства BorderInner и BorderOuter и границы обновляются корректно, поэтому спасибо Рику за то, что он приблизил меня на один шаг.У меня все еще есть проблема при переключении со стиля, который устанавливает свойство BorderBrush, на стиль, который явно не устанавливает границу Brush.

Поэтому у меня все еще есть та же проблема, что и у свойств BorderInnerBrush и BorderOuterBrushработать сразу после установки из кода.Новым является то, что если я затем переключаюсь на стиль, который устанавливает два свойства Brush, это, похоже, снова «разбудит их» ... если я затем переключаюсь на стиль, который не устанавливает границы Brush, внутренние и внешние свойства устанавливают ихзначения по умолчанию снова верны.

Только после использования стиля, который устанавливает два свойства границы из кода позади, они становятся мертвыми для установки в стилях или использования их значений по умолчанию.Это так странно ... кто-нибудь может решить это?

ОБНОВЛЕНИЕ 2 >>>

Во-первых, большое спасибо за ваше время и пример Рика.После копирования и вставки примера кода Рика в новый проект мне пришлось сделать несколько изменений, прежде чем он запустится ... может быть, потому что у меня нет Expression Blend 4?Я установил SDK и добавил ссылки на отмеченные библиотеки, но вместо этого мне пришлось использовать следующие объявления пространства имен XML:

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:ei="clr-namespace:Microsoft.Expression.Interactivity.Core;assembly=Microsoft.Expression.Interactions"

Мне также пришлось немного изменить каждое объявление ChangePropertyAction на:

<ei:ChangePropertyAction TargetName="clock" PropertyName="Style" Value="{StaticResource styleBrush}"/>

Затем, он работал нормально для меня и является хорошим представлением той части моего фактического элемента управления ClockFace, с двумя исключениями.Во-первых, мои стили переключаются из кода через обработчик событий - это явно не вызывает мою проблему.Второе отличие состоит в том, что я установил значения Brush по умолчанию в моих версиях ActualOuterBrush и ActualInnerBrush, так что пользователям элемента управления не нужно предоставлять граничные кисти.

Так что моя последняя проблема заключается в том, что внутренние и стандартные значения по умолчаниюКисти с внешней границей не устанавливаются в Style, свойства границ которых не установлены явно после того, как свойство BorderBrush было установлено в Style.Помните, что значения по умолчанию устанавливаются до этого момента.Итак, я экспериментировал с примером Рика и добавил некоторые значения по умолчанию:

private static readonly DependencyPropertyKey ActualInnerBrushPropertyKey = DependencyProperty.RegisterReadOnly("ActualInnerBrush", typeof(Brush), typeof(Clock), new UIPropertyMetadata(Brushes.Teal));

private static readonly DependencyPropertyKey ActualOuterBrushPropertyKey = DependencyProperty.RegisterReadOnly("ActualOuterBrush", typeof(Brush), typeof(Clock), new UIPropertyMetadata(Brushes.Salmon));

Теперь у этого проекта есть похожая проблема ... значения по умолчанию никогда не сбрасываются.Я так озадачен этим поведением.

Я так долго застрял в этой проблеме, и хотя я узнал много полезных вещей от Рика, у меня все еще есть эта проблема со значениями по умолчанию.Я решил, что самое простое, что нужно сделать - это удалить свойство BorderBrush.Рик настолько помог, что я оцениваю его ответ как правильный ответ, но если кто-нибудь, прочитавший это, может помочь со значением по умолчанию, я был бы рад услышать их.

1 Ответ

0 голосов
/ 29 января 2011

После установки свойства стили больше не будут применяться.Устанавливая свойства внутренней и внешней кисти в измененном обработчике самостоятельно, подсистема свойств зависимостей не осознает, что изменение произошло на самом деле из-за стиля, а вы делали это явно.

Одним из решений является предоставление защищенного чтения-только свойства ActualInnerBorderBrush и ActualOuterBorderBrush, а затем все три определяемых пользователем свойства устанавливают эти фактические значения в соответствующих обработчиках изменений.Таким образом, видимые пользователю свойства всегда могут быть «заданы пользователем», не мешая друг другу.

Редактировать:

Вот полная рабочая реализацияпять свойств:

public class Clock : StackPanel
{
    public Brush Brush
    {
        get { return (Brush)GetValue(BrushProperty); }
        set { SetValue(BrushProperty, value); }
    }

    public static readonly DependencyProperty BrushProperty =
        DependencyProperty.Register("Brush", typeof(Brush), typeof(Clock),
        new UIPropertyMetadata((d, e) => (d as Clock).OnBrushChanged(d, e)));

    public void OnBrushChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ActualInnerBrush = e.NewValue as Brush;
        ActualOuterBrush = e.NewValue as Brush;
    }

    public Brush InnerBrush
    {
        get { return (Brush)GetValue(InnerBrushProperty); }
        set { SetValue(InnerBrushProperty, value); }
    }

    public static readonly DependencyProperty InnerBrushProperty =
        DependencyProperty.Register("InnerBrush", typeof(Brush), typeof(Clock),
        new UIPropertyMetadata((d, e) => (d as Clock).OnInnerBrushChanged(d, e)));

    public void OnInnerBrushChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ActualInnerBrush = e.NewValue as Brush;
    }

    public Brush OuterBrush
    {
        get { return (Brush)GetValue(OuterBrushProperty); }
        set { SetValue(OuterBrushProperty, value); }
    }

    public static readonly DependencyProperty OuterBrushProperty =
        DependencyProperty.Register("OuterBrush", typeof(Brush), typeof(Clock),
        new UIPropertyMetadata((d, e) => (d as Clock).OnOuterBrushChanged(d, e)));

    public void OnOuterBrushChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ActualOuterBrush = e.NewValue as Brush;
    }

    public Brush ActualInnerBrush
    {
        get { return (Brush)GetValue(ActualInnerBrushProperty); }
        private set { SetValue(ActualInnerBrushPropertyKey, value); }
    }

    private static readonly DependencyPropertyKey ActualInnerBrushPropertyKey =
        DependencyProperty.RegisterReadOnly("ActualInnerBrush", typeof(Brush), typeof(Clock), new UIPropertyMetadata());
    public static readonly DependencyProperty ActualInnerBrushProperty = ActualInnerBrushPropertyKey.DependencyProperty;


    public Brush ActualOuterBrush
    {
        get { return (Brush)GetValue(ActualOuterBrushProperty); }
        private set { SetValue(ActualOuterBrushPropertyKey, value); }
    }

    private static readonly DependencyPropertyKey ActualOuterBrushPropertyKey =
        DependencyProperty.RegisterReadOnly("ActualOuterBrush", typeof(Brush), typeof(Clock), new UIPropertyMetadata());
    public static readonly DependencyProperty ActualOuterBrushProperty = ActualOuterBrushPropertyKey.DependencyProperty;
}

и вот небольшая тестовая программа, чтобы доказать, что она работает:

<Grid>
    <Grid.Resources>
        <Style x:Key="styleBrush" TargetType="local:Clock">
            <Setter Property="Brush" Value="Red"/>
        </Style>
        <Style x:Key="styleInnerOnly" TargetType="local:Clock">
            <Setter Property="InnerBrush" Value="Green"/>
        </Style>
        <Style x:Key="styleInnerOuter" TargetType="local:Clock">
            <Setter Property="InnerBrush" Value="Blue"/>
            <Setter Property="OuterBrush" Value="Yellow"/>
        </Style>
        <Style x:Key="styleEmpty" TargetType="local:Clock"/>
    </Grid.Resources>
    <StackPanel>
        <local:Clock x:Name="clock" Orientation="Horizontal">
            <Rectangle Width="100" Height="100" Fill="{Binding ActualInnerBrush, ElementName=clock}"/>
            <Rectangle Width="100" Height="100" Fill="{Binding ActualOuterBrush, ElementName=clock}"/>
        </local:Clock>
        <StackPanel Orientation="Horizontal">
            <Button Content="Default">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="Click">
                        <ei:ChangePropertyAction TargetObject="{Binding ElementName=clock}" PropertyName="Style" Value="{x:Null}"/>
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </Button>
            <Button Content="BrushOnly">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="Click">
                        <ei:ChangePropertyAction TargetObject="{Binding ElementName=clock}" PropertyName="Style" Value="{StaticResource styleBrush}"/>
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </Button>
            <Button Content="InnerOnly">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="Click">
                        <ei:ChangePropertyAction TargetObject="{Binding ElementName=clock}" PropertyName="Style" Value="{StaticResource styleInnerOnly}"/>
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </Button>
            <Button Content="InnerOuter">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="Click">
                        <ei:ChangePropertyAction TargetObject="{Binding ElementName=clock}" PropertyName="Style" Value="{StaticResource styleInnerOuter}"/>
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </Button>
            <Button Content="Empty">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="Click">
                        <ei:ChangePropertyAction TargetObject="{Binding ElementName=clock}" PropertyName="Style" Value="{StaticResource styleEmpty}"/>
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </Button>
        </StackPanel>
    </StackPanel>
</Grid>

В этом примере используются поведения.Если вы не знакомы с поведением, установите Expression Blend 4 SDK и добавьте следующие пространства имен:

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"

и добавьте System.Windows.Interactivity и Microsoft.Expression.Interactions в свой проект.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...