none
WPF. INotifyPropertyChanged и ColorAnimation. RRS feed

  • Вопрос

  • Доброго времени суток.

    Имеется маленький пример.

    Основное окно представляет из себя две кнопки. 

    Одна экспериментальная с измененным стилем. Вторая просто с обработчиком.

    MainWindow.xaml:

    <Window ...>
        <Grid>
            <Button Name="TBlock" Background="Aqua" Foreground="Black" Content="text" Margin="0,0,0,56">
                <Button.Template>
                    <ControlTemplate TargetType="Button">
                        <Grid>
                            <Rectangle x:Name="rectMy" Fill="Red"/>
                            <TextBlock x:Name="tttttt" Text="{TemplateBinding Content}" Foreground="{Binding Source={x:Static Application.Current}, Path=ApplicationData.MyMainColor}"/>
                        </Grid>
                        <ControlTemplate.Triggers>
                            <EventTrigger RoutedEvent="Mouse.MouseEnter">
                                <BeginStoryboard>
                                    <Storyboard>
                                        <ColorAnimation Storyboard.TargetName="tttttt" Storyboard.TargetProperty="Foreground.Color" Duration="0:0:0.3" To="#FF0000FF"/>
                                    </Storyboard>
                                </BeginStoryboard>
                            </EventTrigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Button.Template>
            </Button>
            <Button Click="Button_Click" Margin="0,261,0,0">tmp</Button>
        </Grid>
    </Window>



    В App.Xaml.cs есть класс унаследованный от INotifyPropertyChanged.

    App.Xaml.cs:

    public partial class App : Application
        {
            public string mY;
            ApplicationProperty ap = new ApplicationProperty() { MyMainColor = Colors.Aqua.ToString() };
            public ApplicationProperty ApplicationData
            {
                get { return ap; }
                set { ap = value; }
            }
        }
    
        public class ApplicationProperty : INotifyPropertyChanged
        {
            public event PropertyChangedEventHandler PropertyChanged;
            private string mainThemeColor;
            public string MyMainColor
            {
                get { return mainThemeColor; }
                set
                {
                    mainThemeColor = value;
                    OnPropertyChanged("MyMainColor");
                }
            }
            protected void OnPropertyChanged(string name)
            {
                PropertyChangedEventHandler handler = this.PropertyChanged;
                if (handler != null)
                    handler(this, new PropertyChangedEventArgs(name));
            }
        }


    На обработчике висит изменение свойства:

    private void Button_Click(object sender, RoutedEventArgs e)
            {
                (Application.Current as App).ApplicationData.MyMainColor = Colors.Blue.ToString();
            }

    В таком варианте все прекрасно работает. Проблема в том , что когда я пытаюсь задать

    <ColorAnimation Storyboard.TargetName="tttttt" 
                                                        Storyboard.TargetProperty="Foreground.Color" 
                                                        Duration="0:0:0.3" 
                                                        To="{Binding Source={x:Static Application.Current}, Path=ApplicationData.MyMainColor}"/>

    Приложение вылетает с XamlParseException, и приговаривает "Задание свойства "System.Windows.Controls.Control.Template" вызвало исключение.".

    Есть ли выходы использовать свойство MyMainColor в ColorAnimation.To? Может я что то делаю не так?


    Обычно влезаю в сложности...

    19 ноября 2012 г. 20:29

Ответы

  • Искренне каюсь, за то, что погнал вас такими огородами, но в качестве оправдания скажу, что мне трудно помогать пишущим на C# и вы походу решения задачи получили новые знание, а это хорошо.

    Итак как я сейчас вижу решение этой задачи. Пошагово:

    1. Наследуем нужный элемент.

    2. Делаем ему желаемый шаблон (без анимаций).

    3. Читаем в вышеупомянутой книге все ту же главу, но раздел "Создание элемента управления лишенного внешнего вида".

    4. Из прочитанного извлекаем умение обращаться к именованным частям нашего шаблона из кода.

    5. В коде подвязываемся на событие входа и выхода мыши, а в этих событиях извлекаем из шаблона элемент TextBlock и выполняем анимацию его Foreground.Color.

    6. ну и наконец что же писать в этот долбаный "To"? Тут есть два варианта на ваше усмотрение.

    Первый - в первом посте вы дали простой XAML. Если вы сейчас его протестируете, то увидите, что изменение свойства Foreground кнопки не влияет на цвет текста в ней, потому что по классике ваш TextBlock должен иметь привязку TemplateBinding к этому свойству. Что бы он не простаивал мы можем брать значение для "To" именно из Foreground кнопки, а ему в свою задать стиль

    <SolidColorBrush x:Key="MyColor" Color="{Binding Source={x:Static Application.Current}, Path=ApplicationData.MyMainColor}"/>

    но в этом варианте возникает путаница, так как в Foreground мы привыкли видеть текущий цвет, а не тот что будет в конце анимации.

    Вариант 2 - Мы добавляем элементу свойство, например ToColor, в которому и назначим стиль, что я написал выше, а в качестве конечной точки анимации используем его. И тогда в Foreground можно использовать по назначению, то есть для цвета который отображается в обычном состоянии.

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


    Женат на WPF. Тайно встречаюсь с WinRT. Не сложилось с C#!

    • Помечено в качестве ответа iam93 21 ноября 2012 г. 16:38
    • Снята пометка об ответе iam93 21 ноября 2012 г. 16:38
    • Помечено в качестве ответа iam93 21 ноября 2012 г. 16:44
    • Снята пометка об ответе iam93 21 ноября 2012 г. 18:56
    • Помечено в качестве ответа iam93 21 ноября 2012 г. 18:56
    21 ноября 2012 г. 15:13
    Отвечающий
  • Как же вас судить , если вы мне помогаете) Наоборот хорошо, может что в голове и останется, плюс это весело и мне нравится) Спасибо огромное) Пойду в попытки)

    Великолепно!) Оно работает!!!)

    Сделал с первым вариантом, пока не мешает)

    Код может понадобится кому :

    public MainWindow()
            {
                InitializeComponent();
                if (TBlock.ApplyTemplate())
                {
                     tb = (TextBlock)TBlock.Template.FindName("tttttt", TBlock);
                }
            }
            TextBlock tb;
    private void TBlock_MouseEnter(object sender, MouseEventArgs e)
            {
                
                SolidColorBrush my = (SolidColorBrush)new BrushConverter().ConvertFromString((Application.Current as App).ApplicationData.MyMainColor.ToString());
                ColorAnimation cr = new ColorAnimation(my.Color, new Duration(TimeSpan.FromSeconds(0.5)));
    
                Storyboard s = new Storyboard();
                s.Children.Add(cr);
    
                Storyboard.SetTarget(cr,tb);
                Storyboard.SetTargetProperty(cr, new PropertyPath("Foreground.Color"));
    
                s.Begin();
            }
    
            private void TBlock_MouseLeave(object sender, MouseEventArgs e)
            {
                SolidColorBrush my = (SolidColorBrush)new BrushConverter().ConvertFromString((Application.Current as App).ApplicationData.MyMainColor.ToString());
                ColorAnimation cr = new ColorAnimation(Colors.Gray, new Duration(TimeSpan.FromSeconds(0.5)));
                
                Storyboard s = new Storyboard();
                s.Children.Add(cr);
    
                Storyboard.SetTarget(cr, tb);
                Storyboard.SetTargetProperty(cr, new PropertyPath("Foreground.Color"));
    
                s.Begin();
            }

    Теперь осталось перенести это в основной проект... А там ууу сколько еще всего и проблем в том числе)

    Спасибо)))


    Обычно влезаю в сложности...



    • Изменено iam93 21 ноября 2012 г. 18:56
    • Помечено в качестве ответа iam93 21 ноября 2012 г. 18:56
    21 ноября 2012 г. 16:39

Все ответы

  • Правильно сделали, что внесли вопрос в новый топик.

    Указание привязки в To работает, если ColorAnimation не является частью шаблона. В шаблоне же возникает эта ошибка.

    Можно попробовать задать привязку в ресурсе SolidColorBrush а анимацию делать с помощью ColorAnimationUsingKeyFrames. Подробнее об анимации по ключевым кадрам читайте здесь. А пример использования ColorAnimationUsingKeyFrames можно глянуть в примере шаблона для Button.


    Женат на WPF. Тайно встречаюсь с WinRT. Не сложилось с C#!

    20 ноября 2012 г. 3:18
    Отвечающий
  • Чуть подробнее можно? В частности ресурса SolidColorBrush. SolidColorBrush биндим к нашему AppData.Property? А затем используем SolidColorBrush как статический в ColorAnimationUsingKeyFrames?

    Идиот внутри меня верит, что есть решение, и оно приживется в моем проекте...

    Сделал я вот так, но должно быть не так, компилятор так говорит) 

    <Window.Resources>
            <SolidColorBrush x:Key="MySolidBrush" Color="Aqua"/>
        </Window.Resources>
        <Grid>
            <Button Name="TBlock" Background="Aqua" Foreground="Black" Content="text" Margin="0,0,0,56">
                <Button.Template>
                    <ControlTemplate TargetType="Button">
                        <Grid>
                            <Rectangle x:Name="rectMy" Fill="Black"/>
                            <TextBlock x:Name="tttttt" Text="{TemplateBinding Content}" Foreground="Green"/>
                        </Grid>
                        <ControlTemplate.Triggers>
                            <EventTrigger RoutedEvent="Mouse.MouseEnter">
                                <BeginStoryboard>
                                    <Storyboard>
                                        <ColorAnimationUsingKeyFrames Storyboard.TargetName="tttttt" 
                                                                      Storyboard.TargetProperty="Foreground.Color"
                                                                      BeginTime="00:00:00">
                                            <EasingColorKeyFrame KeyTime="00:00:01" Value="Red"></EasingColorKeyFrame>
                                            <EasingColorKeyFrame KeyTime="00:00:03" Value="Blue"></EasingColorKeyFrame>
                                            <EasingColorKeyFrame KeyTime="00:00:06" Value="{Binding Source={StaticResource MySolidBrush}, Path=Color }"></EasingColorKeyFrame>
                                        </ColorAnimationUsingKeyFrames>
                                        <!--<ColorAnimation Storyboard.TargetName="tttttt" 
                                                        Storyboard.TargetProperty="Foreground.Color" 
                                                        Duration="0:0:0.3" 
                                                        To="{Binding Source={x:Static Application.Current}, Path=ApplicationData.MyMainColor}"/>-->
                                    </Storyboard>
                                </BeginStoryboard>
                            </EventTrigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Button.Template>
            </Button>
            <Button Click="Button_Click" Margin="0,261,0,0">tmp</Button>
        </Grid>
    Простите , утро , гоню может где то... (

    Обычно влезаю в сложности...





    • Изменено iam93 20 ноября 2012 г. 5:53
    20 ноября 2012 г. 4:54
  • Простите , утро , гоню может где то... (

    Обычно влезаю в сложности...

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

    Женат на WPF. Тайно встречаюсь с WinRT. Не сложилось с C#!

    20 ноября 2012 г. 8:39
    Отвечающий
  • Вот и я о том же... Не хочет оно привязываться, ибо Storyboard.Frezable... Ужас какой то) но надежда не умирает) Сколько в поисковике я перебрал результатов, ответа так и не нашел. Единственный вывод - НЕЛЬЗЯ использовать динамический ресурс в Animation , хоть и логика отказывается в это верить...

    Сегодня вот наткнулся, в Английском не очень силен, пытаюсь ковырять о чем они там говорят)

    Вот здесь тоже человек мучался, с мая 2011, так ответа и нет...

    Печально как то(

    Я так из интереса неделю в попытках сижу, к остальному в проекте не прикасаюсь, интересно ведь)))


    Обычно влезаю в сложности...


    • Изменено iam93 20 ноября 2012 г. 9:51
    20 ноября 2012 г. 9:47
  • ибо Storyboard.Frezable...

    Да дело не в этом. Если Storyboard зададим в обычных триггерах, то привязка отлично работает, а вот в ControlTemplate возникает исключение. Буду искать ответ и может кто еще поможет.

    Женат на WPF. Тайно встречаюсь с WinRT. Не сложилось с C#!

    20 ноября 2012 г. 10:52
    Отвечающий
  • Как показало повторение мат базы такая с виду элементарная задача называется "Специальным рисованием" и там все чуть сложнее.

    Что я вам посоветую - купите ;) книгу Мэтью Макдонольда "WPF in C# 2010 для профессионалов". Там в главе 18 в самом конце есть раздел "Рисованные элементы", возможно это поможет.


    Женат на WPF. Тайно встречаюсь с WinRT. Не сложилось с C#!

    20 ноября 2012 г. 16:14
    Отвечающий
  • Ознакомился с разделом "Рисованные элементы".Получается мне нужно создать класс унаследованный от FrameworkElement?

    Вот создам я класс унаследованный от FrameworkElement, будет там ForegrounColorProperty , и опять все сведется к тому что во время анимации прийдеться использовать привязку, которую он ни как не "ест". Или вы считаете что минуя ControlTemplate привязка нормально сработает?) 

    Подправьте если мысли пошли не в то русло....

    Огромное спасибо за помощь)


    Обычно влезаю в сложности...




    • Изменено iam93 20 ноября 2012 г. 20:04
    20 ноября 2012 г. 17:51
  • Из содержимого раздела "Рисованные элементы" главное что я извлек это то, что рисование выполняется в коде а не в шаблоне.

    Дело тут в том, что при рисовании в XAML есть ряд ограничений, так как он строится полностью при выполнении проекта. Рисование же в коде выполняется только тогда тогда нужно, то есть уже в время работы выполнения приложения.Вы добавите свойство ForegrounColor элементу и зададите ранее упомянутую привязку, а в коде используя значение ForegrounColor рисуете анимацию. Вот у вас и получится свой контролл ButtonColorAnimation. 


    Женат на WPF. Тайно встречаюсь с WinRT. Не сложилось с C#!

    21 ноября 2012 г. 2:41
    Отвечающий
  • Итак класс есть :

    public class ButtonColorAnimation : FrameworkElement
            {
                public static DependencyProperty BackgroundColorProperty;
                public static DependencyProperty ForegroundColorProperty;
                public static DependencyProperty ContentProperty;
                static ButtonColorAnimation()
                {
                    FrameworkPropertyMetadata metadata = new FrameworkPropertyMetadata(Colors.Aqua);
                    metadata.AffectsRender = true;
                    BackgroundColorProperty = DependencyProperty.Register("BackgroundColor", typeof(Color), typeof(ButtonColorAnimation), new FrameworkPropertyMetadata(Colors.Black));
                    ContentProperty = DependencyProperty.Register("Cotent", typeof(string), typeof(ButtonColorAnimation), new FrameworkPropertyMetadata("Content"));
                    ForegroundColorProperty = DependencyProperty.Register("ForagroundColor", typeof(Color), typeof(ButtonColorAnimation), metadata);
                }
    
                public Color BackgroundColor 
                {
                    get { return (Color)GetValue(BackgroundColorProperty); }
                    set { SetValue(BackgroundColorProperty, value); }
                }
                public string Content
                {
                    get { return (string)GetValue(ContentProperty); }
                    set { SetValue(ContentProperty, value); }
                }
                public Color ForegroundColor
                {
                    get { return (Color)GetValue(BackgroundColorProperty); }
                    set { SetValue(BackgroundColorProperty, value); }
                }
    
    
                protected override void OnRender(DrawingContext drawingContext)
                {
                    base.OnRender(drawingContext);
                    Rect bonds = new Rect(0, 0, base.ActualWidth, base.ActualHeight);
                    drawingContext.DrawRectangle(getBackgroudBrush(), null, bonds);
                    drawingContext.DrawText(new FormattedText(Content, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, new Typeface("Arial"), 15.0, getForegroudBrush()), new Point(50, 50));
                }
    
                private Brush getBackgroudBrush()
                {
                    return new SolidColorBrush(BackgroundColor);
                }
    
                private Brush getForegroudBrush()
                {
                    if (!IsMouseOver)
                    {
                        return new SolidColorBrush(Colors.Gray);
                    }
                    else
                    {
                        SolidColorBrush brush = new SolidColorBrush(Colors.Aqua);
                        return brush;
                    }
                }
            }
    При наведении мы сразу меняем Foregrоund, не подскажите как менять его плавно? Анимация в общем...


    Обычно влезаю в сложности...


    • Изменено iam93 21 ноября 2012 г. 12:25
    21 ноября 2012 г. 12:25
  • Искренне каюсь, за то, что погнал вас такими огородами, но в качестве оправдания скажу, что мне трудно помогать пишущим на C# и вы походу решения задачи получили новые знание, а это хорошо.

    Итак как я сейчас вижу решение этой задачи. Пошагово:

    1. Наследуем нужный элемент.

    2. Делаем ему желаемый шаблон (без анимаций).

    3. Читаем в вышеупомянутой книге все ту же главу, но раздел "Создание элемента управления лишенного внешнего вида".

    4. Из прочитанного извлекаем умение обращаться к именованным частям нашего шаблона из кода.

    5. В коде подвязываемся на событие входа и выхода мыши, а в этих событиях извлекаем из шаблона элемент TextBlock и выполняем анимацию его Foreground.Color.

    6. ну и наконец что же писать в этот долбаный "To"? Тут есть два варианта на ваше усмотрение.

    Первый - в первом посте вы дали простой XAML. Если вы сейчас его протестируете, то увидите, что изменение свойства Foreground кнопки не влияет на цвет текста в ней, потому что по классике ваш TextBlock должен иметь привязку TemplateBinding к этому свойству. Что бы он не простаивал мы можем брать значение для "To" именно из Foreground кнопки, а ему в свою задать стиль

    <SolidColorBrush x:Key="MyColor" Color="{Binding Source={x:Static Application.Current}, Path=ApplicationData.MyMainColor}"/>

    но в этом варианте возникает путаница, так как в Foreground мы привыкли видеть текущий цвет, а не тот что будет в конце анимации.

    Вариант 2 - Мы добавляем элементу свойство, например ToColor, в которому и назначим стиль, что я написал выше, а в качестве конечной точки анимации используем его. И тогда в Foreground можно использовать по назначению, то есть для цвета который отображается в обычном состоянии.

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


    Женат на WPF. Тайно встречаюсь с WinRT. Не сложилось с C#!

    • Помечено в качестве ответа iam93 21 ноября 2012 г. 16:38
    • Снята пометка об ответе iam93 21 ноября 2012 г. 16:38
    • Помечено в качестве ответа iam93 21 ноября 2012 г. 16:44
    • Снята пометка об ответе iam93 21 ноября 2012 г. 18:56
    • Помечено в качестве ответа iam93 21 ноября 2012 г. 18:56
    21 ноября 2012 г. 15:13
    Отвечающий
  • Как же вас судить , если вы мне помогаете) Наоборот хорошо, может что в голове и останется, плюс это весело и мне нравится) Спасибо огромное) Пойду в попытки)

    Великолепно!) Оно работает!!!)

    Сделал с первым вариантом, пока не мешает)

    Код может понадобится кому :

    public MainWindow()
            {
                InitializeComponent();
                if (TBlock.ApplyTemplate())
                {
                     tb = (TextBlock)TBlock.Template.FindName("tttttt", TBlock);
                }
            }
            TextBlock tb;
    private void TBlock_MouseEnter(object sender, MouseEventArgs e)
            {
                
                SolidColorBrush my = (SolidColorBrush)new BrushConverter().ConvertFromString((Application.Current as App).ApplicationData.MyMainColor.ToString());
                ColorAnimation cr = new ColorAnimation(my.Color, new Duration(TimeSpan.FromSeconds(0.5)));
    
                Storyboard s = new Storyboard();
                s.Children.Add(cr);
    
                Storyboard.SetTarget(cr,tb);
                Storyboard.SetTargetProperty(cr, new PropertyPath("Foreground.Color"));
    
                s.Begin();
            }
    
            private void TBlock_MouseLeave(object sender, MouseEventArgs e)
            {
                SolidColorBrush my = (SolidColorBrush)new BrushConverter().ConvertFromString((Application.Current as App).ApplicationData.MyMainColor.ToString());
                ColorAnimation cr = new ColorAnimation(Colors.Gray, new Duration(TimeSpan.FromSeconds(0.5)));
                
                Storyboard s = new Storyboard();
                s.Children.Add(cr);
    
                Storyboard.SetTarget(cr, tb);
                Storyboard.SetTargetProperty(cr, new PropertyPath("Foreground.Color"));
    
                s.Begin();
            }

    Теперь осталось перенести это в основной проект... А там ууу сколько еще всего и проблем в том числе)

    Спасибо)))


    Обычно влезаю в сложности...



    • Изменено iam93 21 ноября 2012 г. 18:56
    • Помечено в качестве ответа iam93 21 ноября 2012 г. 18:56
    21 ноября 2012 г. 16:39