Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > WPF и Silverlight > Бегущая строка


Автор: AleksPingvin 24.5.2011, 07:00
Доброе утро, друзья!

Продолжаю задавать глупые вопросы по WPF..(предварительно разумеется гуглю).
Хотелось бы услышать ваше профессиональное мнение по реализации одного компонента.

Согласно поставленной мне задаче в программе должна быть бегущая строка. 
По скольку она должна быть довольно сильно стилизована (сложный фон, двигается не только текст но и пиктограммы), то я решил делать это следующим образом. Создал свой класс CreepLine унаследовав его от Canvas, объявил нужные DependencyProperty, перегрузил GetVisualChild, VisualChildrenCount. 
Объявил объект DrawingVisual, который добавляю на канву через AddVisualChild и AddLogicalChild, на нем я и хочу рисовать все что мне надо. 
Далее я хочу отрендерить данные и clip-ать их по нужному прямоугольнику. А в анимации менять свойство которое будет отвечать за смещение прямоугольника обрезки.

Вопрос на сколько оправдана такая реализация и вообще возможно ли она? Не будет ли проблем с производительностью?
Быть может есть более "гуманный" способ реализации бегущей строки? К сожалению в интернете я ни где не смог найти реализации бегущей строки на wpf, разве что только в текстовом поле DataGrid, но мне это ведь не катит...

С Уважением,
Александр

Автор: -Mikle- 24.5.2011, 13:17
Цитата(AleksPingvin @  24.5.2011,  10:00 Найти цитируемый пост)
К сожалению в интернете я ни где не смог найти реализации бегущей строки на wpf

Я тоже реально толкового не нашел, поэтому сделал свой. Код выложить не могу (права конторы), но опишу использованную идею, как время будет.

Автор: AleksPingvin 24.5.2011, 13:48
Буду ждать с нетерпением.

Автор: AleksPingvin 24.5.2011, 21:16
Кстати попробовал реализовать идею и неожиданно столкнулся с проблемой создания пустого изображения. Хочу сделать следующее:
1) Рассчитать ширину изображения, путем сложения ширины текста (FormattedText мне в помощь) и пиктограм + margin.
2) Создать изображение и отрисовать на нем текст и строку
3) Согласно текущему смещению отрендерить путем Clipin-ga в методе OnRender на мой компонент .

Вот ни как не найду нужные классы для пункта 2.. не подскажете ли? Видел пример с использованием Canvas, которая потом подается в RenderTargetBitmap , но что-то мне подсказывает, что это не правильный путь....

Автор: DenWPF 24.5.2011, 22:27
Не понимаю в чем сложность. берете текст бокс убераете у него стили. строку в цикле по символу вызываете...

Автор: AleksPingvin 25.5.2011, 02:04
Мне не нужен текстбокс.
У меня бегущая строка со сложным фоном, пиктограммами и плавной (а не сдвиг по символу) анимацией.

Автор: -Mikle- 25.5.2011, 06:07
AleksPingvin, в моей реализации любой контент можно прокручивать, это подходит? Может седня вечером работы меньше будет, напишу.

Автор: AleksPingvin 25.5.2011, 07:30
-Mikle-, если она исполняет свою главную роль - текст бежит и можно использовать пиктограмки в нем, то да. 
Впрочем все равно опишите идею, лишним не будет.
А заодно пока не подскажете ли как все же для общего развития, как мне программно (а не загрузив из файла) создать изображение на котором я потом смогу программно отрисовать все что захочу smile

Автор: DenWPF 25.5.2011, 15:12
Я конечно не сильно понял, ну а такой вариант не подходит?

если нет, то есть пример того чего ты хочешь?
Код

<StackPanel Width="700" Height="300">
        <Label Name="label1" Content="Бегущая строка" FontSize="32" />
        <Button HorizontalAlignment="Center" Click="Button_Click" Content="Запустить" />
        <Button HorizontalAlignment="Center" Click="Button_Click_1" Content="Остановить" />
    </StackPanel>


Код

private void Button_Click(object sender, RoutedEventArgs e)
        {
            ThicknessAnimation ta = new ThicknessAnimation();
            ta.From = new Thickness(600, 0, 0, 0);
            ta.To = new Thickness(0, 0, 0, 0);
            ta.Duration = TimeSpan.FromMilliseconds(3000);
            ta.RepeatBehavior = RepeatBehavior.Forever;
            label1.BeginAnimation(Label.MarginProperty, ta);
        }
 
        private void Button_Click_1(object sender, RoutedEventArgs e)
        {
            label1.BeginAnimation(Label.MarginProperty, null);
        }

Автор: AleksPingvin 25.5.2011, 16:18
Код не запускал, ибо на работе, но на сколько я вижу:
1) Бежит только текст. Я в третий раз говорю - нужно иметь возможность задавать в качестве объекта движения не только текст, но и пикторграммы.
Например: Игра начинается в <пиктограма будильника> 12.00 бла бла бла.
2) Когда строка кончается, то дальше она идет заново не ожидая того, когда ее остаток дойдет до конца. Иными словами если строка "Привет, я дядя Степа!" и вся фраза не влазит  в ширину строки, то будет выглядеть так: "вет, я дядя Степа! При"

Автор: DenWPF 25.5.2011, 17:11
бежит не текст, а Label т.е Контрол, можно любой контрол так.
Сделай обертку с областью видимостью.
ладно, пусть -Mikle- напишет как у него. 

Автор: -Mikle- 25.5.2011, 18:46
Цитата(DenWPF @  25.5.2011,  20:11 Найти цитируемый пост)
Сделай обертку с областью видимостью.
ладно, пусть -Mikle- напишет как у него.

Ну именно так и сделано у меня. Я решил смотреть в наш контрол и написать новый, но только изрядно урезанный вариант. Тем самым я и весь основной код не выложу (сохраню права на код), но в то же время то что требуется именно топикастеру реализую. В связи с копи-пастами из более навороченного контрола некоторая логика, возможно, будет излишней. Правьте сами, кому не нравится smile.

Контрол делался для силверлайта, но я не предвижу никаких проблем сделать то же самое на WPF.
Итак...

Создаем Templated Control и даем имя CreepLine. Студия нам создает CreepLine.cs и Generic.xaml (в нем будет определяться шаблон контрола). Далее меняем код класса на вот это:
Код

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Threading;

[TemplatePart(Name = PART_CONTAINER, Type = typeof(FrameworkElement))]
[TemplatePart(Name = PART_CONTENT, Type = typeof(FrameworkElement))]
public class CreepLine : ContentControl
{
    private const string PART_CONTAINER = "CONTAINER_PART";
    private const string PART_CONTENT = "CONTENT_PART";

    public const string NormalState = "Normal";
    public const string MarqueeState = "Marquee";

    FrameworkElement container;
    FrameworkElement content;
    string gotoStateName = null;
    DispatcherTimer delayTimer;

    public CreepLine()
    {
        this.DefaultStyleKey = typeof(CreepLine);

        delayTimer = new DispatcherTimer();
        delayTimer.Tick += delayTimer_Tick;
        delayTimer.Interval = TimeSpan.FromMilliseconds(1);
        delayTimer.Stop();
        OnInitialDelayChanged(TimeSpan.Zero, TimeSpan.Zero);

        IsEnabled = false;

        SizeChanged += delegate { ClipToBounds(); };
        Loaded += delegate
        {
            ClipToBounds();
            CalculateProperties(true, true);
        };
        IsEnabledChanged += delegate
        {
            if (IsEnabled) InvalidateState();
            else ImmediatllyGoToState(NormalState);
        };
    }

    protected override Size MeasureOverride(Size availableSize)
    {
        FrameworkElement root = this.Content as FrameworkElement;
        if (root == null) return base.MeasureOverride(availableSize);
        root.Measure(availableSize);

        Size resultSize = availableSize;
        switch (HorizontalAlignment)
        {
            case System.Windows.HorizontalAlignment.Left:
            case System.Windows.HorizontalAlignment.Center:
            case System.Windows.HorizontalAlignment.Right:
                resultSize.Width = !double.IsInfinity(availableSize.Width) ? Math.Min(availableSize.Width, root.DesiredSize.Width) : root.DesiredSize.Width;
                break;
            case System.Windows.HorizontalAlignment.Stretch:
            default:
                resultSize.Width = !double.IsInfinity(availableSize.Width) ? availableSize.Width : root.DesiredSize.Width;
                break;
        }

        switch (VerticalAlignment)
        {
            case System.Windows.VerticalAlignment.Top:
            case System.Windows.VerticalAlignment.Center:
            case System.Windows.VerticalAlignment.Bottom:
                resultSize.Height = !double.IsInfinity(availableSize.Height) ? Math.Min(availableSize.Height, root.DesiredSize.Height) : root.DesiredSize.Height;
                break;
            case System.Windows.VerticalAlignment.Stretch:
            default:
                resultSize.Height = !double.IsInfinity(availableSize.Height) ? availableSize.Height : root.DesiredSize.Height;
                break;
        }

        return resultSize;
    }

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        container = GetTemplatePart<FrameworkElement, Canvas>(PART_CONTAINER);
        content = GetTemplatePart<FrameworkElement, ContentControl>(PART_CONTENT);
        CalculateProperties(true, true);

        content.SizeChanged += delegate
        {
            this.InvalidateMeasure();
            CalculateProperties(false, true);
        };
        container.SizeChanged += delegate
        {
            this.InvalidateMeasure();
            CalculateProperties(true, false);
        };
    }

    private T GetTemplatePart<T, IfNotFoundType>(string templateName)
        where T : class
        where IfNotFoundType : T, new()
    {
        return (this.GetTemplateChild(templateName) as T) ?? new IfNotFoundType();
    }

    private void ClipToBounds()
    {
        Clip = new RectangleGeometry() { Rect = new Rect(0, 0, ActualWidth, ActualHeight) };
    }

    private void CalculateProperties(bool containerChanged, bool contentChanged)
    {
        if (container == null || content == null) return;

        if (containerChanged)
        {
            StartContentAtRight = container.ActualWidth;
        }
        if (contentChanged)
        {
            EndContentAtLeft = -content.ActualWidth;
            this.MinHeight = content.ActualHeight;
        }
        if (containerChanged || contentChanged)
        {
            EndContentAtRight = container.ActualWidth - content.ActualWidth;
            CalculateDuration();
            //makesSenseToAnimate = EndContentAtRight < 0;
            InvalidateState();
        }
    }
    private void CalculateDuration()
    {
        if (container == null || content == null) return;
        Duration = new Duration(
            TimeSpan.FromSeconds(
                (container.ActualWidth * 2 + content.ActualWidth) / PixelsPerSecondSpeed));
    }

    private void delayTimer_Tick(object sender, EventArgs e)
    {
        if (!ImmediatllyGoToState(gotoStateName)) return;

        delayTimer.Stop();
        gotoStateName = null;
    }
    private void InvalidateState()
    {
        GoToState(MarqueeState);
    }
    private void GoToState(string stateName)
    {
        if (!IsEnabled)
        {
            ImmediatllyGoToState(NormalState);
            return;
        }
        if (stateName == NormalState)
            ImmediatllyGoToState(stateName);
        else
            DellayGoToState(stateName);
    }
    private void DellayGoToState(string stateName)
    {
        gotoStateName = stateName;
        delayTimer.Start();
    }
    private bool ImmediatllyGoToState(string stateName)
    {
        try
        {
            CalculateDuration();
            if (string.IsNullOrWhiteSpace(stateName)) stateName = NormalState;
            VisualStateManager.GoToState(this, stateName, true);
            delayTimer.Stop();
            gotoStateName = null;
            return true;
        }
        catch
        {
            return false;
        }
    }


    public static readonly DependencyProperty StartContentAtRightProperty = DependencyProperty.Register("StartContentAtRight", typeof(double), typeof(CreepLine), new PropertyMetadata(0.0));
    public double StartContentAtRight
    {
        get { return (double)GetValue(StartContentAtRightProperty); }
        protected set { SetValue(StartContentAtRightProperty, value); }
    }
    public static readonly DependencyProperty EndContentAtLeftProperty = DependencyProperty.Register("EndContentAtLeft", typeof(double), typeof(CreepLine), new PropertyMetadata(0.0));
    public double EndContentAtLeft
    {
        get { return (double)GetValue(EndContentAtLeftProperty); }
        protected set { SetValue(EndContentAtLeftProperty, value); }
    }
    public static readonly DependencyProperty EndContentAtRightProperty = DependencyProperty.Register("EndContentAtRight", typeof(double), typeof(CreepLine), new PropertyMetadata(0.0));
    public double EndContentAtRight
    {
        get { return (double)GetValue(EndContentAtRightProperty); }
        protected set { SetValue(EndContentAtRightProperty, value); }
    }
    public static readonly DependencyProperty DurationProperty = DependencyProperty.Register("Duration", typeof(Duration), typeof(CreepLine), new PropertyMetadata(Duration.Automatic));
    public Duration Duration
    {
        get { return (Duration)GetValue(DurationProperty); }
        protected set { SetValue(DurationProperty, value); }
    }

    public static readonly DependencyProperty PixelsPerSecondSpeedProperty = DependencyProperty.Register("PixelsPerSecondSpeed", typeof(double), typeof(CreepLine), new PropertyMetadata(75.0, PixelsPerSecondSpeedChanged));
    private static void PixelsPerSecondSpeedChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        ((CreepLine)sender).OnPixelsPerSecondSpeedChanged((double)e.OldValue, (double)e.NewValue);
    }
    public double PixelsPerSecondSpeed
    {
        get { return (double)GetValue(PixelsPerSecondSpeedProperty); }
        set { SetValue(PixelsPerSecondSpeedProperty, value); }
    }
    protected virtual void OnPixelsPerSecondSpeedChanged(double oldValue, double newValue)
    {
        if (newValue < 1)
            PixelsPerSecondSpeed = 1.0;
        else
            CalculateDuration();
    }

    public static readonly DependencyProperty InitialDelayProperty = DependencyProperty.Register("InitialDelay", typeof(TimeSpan), typeof(CreepLine), new PropertyMetadata(TimeSpan.Zero, InitialDelayChanged));
    private static void InitialDelayChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        ((CreepLine)sender).OnInitialDelayChanged((TimeSpan)e.OldValue, (TimeSpan)e.NewValue);
    }
    public TimeSpan InitialDelay
    {
        get { return (TimeSpan)GetValue(InitialDelayProperty); }
        set { SetValue(InitialDelayProperty, value); }
    }
    protected virtual void OnInitialDelayChanged(TimeSpan oldValue, TimeSpan newValue)
    {
        delayTimer.Interval = TimeSpan.FromMilliseconds(Math.Max(1.0, newValue.TotalMilliseconds));
    }
}


Открываем Generic.xaml и находим там стиль для вновь созданного контрола. Редактируем его и приводим в такой вид:
Код

    <Style TargetType="local:CreepLine">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:CreepLine">
                    <Border Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}">

                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="Animations">
                                <VisualState x:Name="Normal">
                                    <Storyboard Storyboard.TargetName="CONTENT_PART" Storyboard.TargetProperty="(Canvas.Left)">
                                        <DoubleAnimation Duration="00:00:00.3" To="0">
                                            <DoubleAnimation.EasingFunction>
                                                <QuinticEase EasingMode="EaseOut" />
                                            </DoubleAnimation.EasingFunction>
                                        </DoubleAnimation>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Marquee">
                                    <Storyboard Storyboard.TargetName="CONTENT_PART" Storyboard.TargetProperty="(Canvas.Left)">
                                        <DoubleAnimation From="{Binding StartContentAtRight, RelativeSource={RelativeSource}}"
                                                         To="{Binding EndContentAtLeft, RelativeSource={RelativeSource}}"
                                                         Duration="{Binding Duration, RelativeSource={RelativeSource}}" RepeatBehavior="Forever" />
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>

                        <Canvas x:Name="CONTAINER_PART">
                            <ContentPresenter x:Name="CONTENT_PART"
                                              Content="{TemplateBinding Content}"
                                              ContentTemplate="{TemplateBinding ContentTemplate}"
                                              HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                              VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                                              Margin="{TemplateBinding Padding}" />
                        </Canvas>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>


Вот и все. Этот контрол выполняет нужную функцию, а именно, анимирует слева на право любой контент. Свойство IsEnabled говорит о том бежит ли контент или стоит. Свойство InitialDelay задает паузу, перед тем как контент начнет бежать (тут наверное лишнее для этой задачи).

Вот пример использования:
Код

            <local:CreepLine IsEnabled="True" InitialDelay="00:00:05">
                <StackPanel Orientation="Horizontal">
                    <Image Source="Favorites.png" Height="16"/>
                    <TextBlock>Hello world!!! This is the simple example of large text. Offcouse, This is not so large as you are needs, but this is someone.</TextBlock>
                    <Image Source="Favorites.png" Height="16"/>
                </StackPanel>
            </local:CreepLine>


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

Автор: AleksPingvin 25.5.2011, 19:18
-Mikle-, супер! Попробую утром перекопипастить и потестить ваш компонент) Если возникнут вопросы - обязательно их задам smile
Кстати а "выбег" содержимого начинается с краю или он сразу появляется а только потом начинает двигаться?

Автор: -Mikle- 25.5.2011, 20:55
Цитата(AleksPingvin @  25.5.2011,  22:18 Найти цитируемый пост)
Кстати а "выбег" содержимого начинается с краю или он сразу появляется а только потом начинает двигаться?

Ну вот завтра и увидишь smile

Не дорассказал еще про свойство PixelsPerSecondSpeed - оно задает скорость перемещения в пикселах (на сколько пикселов двинется контент за одну секунду), по умолчанию стоит 75.

Свойства StartContentAtRight, EndContentAtLeft, EndContentAtRight и Duration - ридонли и служат для поддержки анимации в шаблоне контрола. Ну конечно задать значения этих свойств можно, только не из XAML. Да и незачем стремиться к этому, так как они все равно снова пересчитаются при любом изменении. В WPF можно объявить ридонли депенденси свойства и тогда они действительно будут ридонли, а сильверлайт пока не поддерживает такие.

Автор: AleksPingvin 26.5.2011, 05:51
-Mikle-, ты меня убьешь... но моя VS 2010 не умеет создавать Template Control-ы... нет у нее такого пунктика( Вот User Control да)... я ее даже обновил и сдк поставил и нифига. 

Автор: -Mikle- 26.5.2011, 06:32
Не убью smile. Это значит ты не для сильвера делаешь (я говорил, пример выше для сильвера делался). Для WPF он называется Custom Control. Это то же самое. По идее, этот код должен работать один-в-один для WPF, но я не проверял.

Автор: AleksPingvin 26.5.2011, 07:24
Сделал просто файл ресурсов вставил туда стиль и создал класс CreepLine разместив твой код там... разместил компонент где надо и... ничего нет( просто белое пустое место( ни текста ни анимации... ни даже фона. видимо xaml надо как-то поиному делать всеже надо... 
СustomControl попробую вечером, что то он не создается.. визуалка эксепшен выдает(

Добавлено @ 07:33
Гы. Забыл ресурс подрубить в окно.. но теперь ошибка RelativeSoure.. эксепшен т.е.

Цитата

System.Windows.Markup.XamlParseException was unhandled
  Message=Инициализация "System.Windows.Data.RelativeSource" вызвала исключение.
  Source=PresentationFramework
  LineNumber=0
  LinePosition=0
  StackTrace:
       в System.Windows.Markup.XamlReader.RewrapException(Exception e, Uri baseUri)
       в System.Windows.FrameworkTemplate.LoadTemplateXaml(XamlReader templateReader, XamlObjectWriter currentWriter)
       в System.Windows.FrameworkTemplate.LoadTemplateXaml(XamlObjectWriter objectWriter)
       в System.Windows.FrameworkTemplate.LoadOptimizedTemplateContent(DependencyObject container, IComponentConnector componentConnector, IStyleConnector styleConnector, List`1 affectedChildren, UncommonField`1 templatedNonFeChildrenField)
       в System.Windows.FrameworkTemplate.LoadContent(DependencyObject container, List`1 affectedChildren)
       в System.Windows.StyleHelper.ApplyTemplateContent(UncommonField`1 dataField, DependencyObject container, FrameworkElementFactory templateRoot, Int32 lastChildIndex, HybridDictionary childIndexFromChildID, FrameworkTemplate frameworkTemplate)
       в System.Windows.FrameworkTemplate.ApplyTemplateContent(UncommonField`1 templateDataField, FrameworkElement container)
       в System.Windows.FrameworkElement.ApplyTemplate()
       в System.Windows.FrameworkElement.MeasureCore(Size availableSize)
       в System.Windows.UIElement.Measure(Size availableSize)
       в System.Windows.Controls.DockPanel.MeasureOverride(Size constraint)
       в System.Windows.FrameworkElement.MeasureCore(Size availableSize)
       в System.Windows.UIElement.Measure(Size availableSize)
       в MS.Internal.Helper.MeasureElementWithSingleChild(UIElement element, Size constraint)
       в System.Windows.Controls.ContentPresenter.MeasureOverride(Size constraint)
       в System.Windows.FrameworkElement.MeasureCore(Size availableSize)
       в System.Windows.UIElement.Measure(Size availableSize)
       в System.Windows.Documents.AdornerDecorator.MeasureOverride(Size constraint)
       в System.Windows.FrameworkElement.MeasureCore(Size availableSize)
       в System.Windows.UIElement.Measure(Size availableSize)
       в System.Windows.Controls.Border.MeasureOverride(Size constraint)
       в System.Windows.FrameworkElement.MeasureCore(Size availableSize)
       в System.Windows.UIElement.Measure(Size availableSize)
       в System.Windows.Window.MeasureOverrideHelper(Size constraint)
       в System.Windows.Window.MeasureOverride(Size availableSize)
       в System.Windows.FrameworkElement.MeasureCore(Size availableSize)
       в System.Windows.UIElement.Measure(Size availableSize)
       в System.Windows.Interop.HwndSource.SetLayoutSize()
       в System.Windows.Interop.HwndSource.set_RootVisualInternal(Visual value)
       в System.Windows.Interop.HwndSource.set_RootVisual(Visual value)
       в System.Windows.Window.SetRootVisual()
       в System.Windows.Window.SetRootVisualAndUpdateSTC()
       в System.Windows.Window.SetupInitialState(Double requestedTop, Double requestedLeft, Double requestedWidth, Double requestedHeight)
       в System.Windows.Window.CreateSourceWindow(Boolean duringShow)
       в System.Windows.Window.CreateSourceWindowDuringShow()
       в System.Windows.Window.SafeCreateWindowDuringShow()
       в System.Windows.Window.ShowHelper(Object booleanBox)
       в System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
       в MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
       в System.Windows.Threading.DispatcherOperation.InvokeImpl()
       в System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(Object state)
       в System.Threading.ExecutionContext.runTryCode(Object userData)
       в System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode code, CleanupCode backoutCode, Object userData)
       в System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
       в System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
       в System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       в System.Windows.Threading.DispatcherOperation.Invoke()
       в System.Windows.Threading.Dispatcher.ProcessQueue()
       в System.Windows.Threading.Dispatcher.WndProcHook(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
       в MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
       в MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
       в System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
       в MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
       в System.Windows.Threading.Dispatcher.InvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
       в MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
       в MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
       в System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
       в System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
       в System.Windows.Threading.Dispatcher.Run()
       в System.Windows.Application.RunDispatcher(Object ignore)
       в System.Windows.Application.RunInternal(Window window)
       в System.Windows.Application.Run(Window window)
       в System.Windows.Application.Run()
       в Present.App.Main() в C:\WPF_D\Sample\obj\x86\Debug\App.g.cs:строка 0
       в System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
       в System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
       в Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
       в System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       в System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
       в System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       в System.Threading.ThreadHelper.ThreadStart()
  InnerException: System.InvalidOperationException
       Message=Необходимо указать Mode для RelativeSource.
       Source=PresentationFramework
       StackTrace:
            в System.Windows.Data.RelativeSource.System.ComponentModel.ISupportInitialize.EndInit()
            в MS.Internal.Xaml.Runtime.ClrObjectRuntime.InitializationGuard(XamlType xamlType, Object obj, Boolean begin)
       InnerException: 


Автор: DenWPF 26.5.2011, 08:09
Если проект чистый залей его.

Автор: AleksPingvin 26.5.2011, 08:37
Почищу.. но в принципе можно просто сделать пустой проект, добавить файл ресурсов, в который вставить код стиля приведенного Майклом и создать класс вставив его же исходники... а в главном окне вставить компонент...

Автор: -Mikle- 26.5.2011, 11:39
Ну если у тебя не получится, я вечерком в WPF сам перенесу код и выложу сюда. Возможно это все таки из-за неполной совместимости WPF и Silverlight.

Автор: AleksPingvin 26.5.2011, 19:45
Не получается( Ошибка и все тут( Вся надежда на тебя...

Автор: -Mikle- 27.5.2011, 08:08
Да, выяснилось, что полной совместимости между WPF и Silverlight нету. В XAML коде надо сделать пару изменений:
1) RelativeSource нужно обязательно объявить с явно заданным режимом: RelativeSource={RelativeSource TemplatedParent}
2) Чтобы относительный источник был найден, биндинг должен (почему-то, в отличие от сильверлайт) находиться в зоне досягаемости визуального дерева. Чтоб этого достичь, можно перенести анимацию в ресурсы корневого элемента шаблона.

В итоге, шаблон теперь выглядит вот так:
Код

    <Style TargetType="local:CreepLine">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:CreepLine">
                    <Border Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}">
                        <Border.Resources>
                            <Storyboard x:Key="marqueeStoryboard" Storyboard.TargetName="CONTENT_PART" Storyboard.TargetProperty="(Canvas.Left)">
                                <DoubleAnimation From="{Binding StartContentAtRight, RelativeSource={RelativeSource TemplatedParent}}"
                                                 To="{Binding EndContentAtLeft, RelativeSource={RelativeSource TemplatedParent}}"
                                                 Duration="{Binding Duration, RelativeSource={RelativeSource TemplatedParent}}" RepeatBehavior="Forever"/>
                            </Storyboard>
                            <Storyboard x:Key="normalStoryboard" Storyboard.TargetName="CONTENT_PART" Storyboard.TargetProperty="(Canvas.Left)">
                                <DoubleAnimation Duration="00:00:00.3" To="0">
                                    <DoubleAnimation.EasingFunction>
                                        <QuinticEase EasingMode="EaseOut"/>
                                    </DoubleAnimation.EasingFunction>
                                </DoubleAnimation>
                            </Storyboard>
                        </Border.Resources>

                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="Animations">
                                <VisualState x:Name="Normal" Storyboard="{StaticResource normalStoryboard}"/>
                                <VisualState x:Name="Marquee" Storyboard="{StaticResource marqueeStoryboard}"/>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>

                        <Canvas x:Name="CONTAINER_PART">
                            <ContentPresenter x:Name="CONTENT_PART" Canvas.Left="0" Canvas.Top="0"
                                              Content="{TemplateBinding Content}"
                                              ContentTemplate="{TemplateBinding ContentTemplate}"
                                              HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                              VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                                              Margin="{TemplateBinding Padding}"/>
                        </Canvas>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>


Код логики контрола остается без изменений.

На всякий случай, прикрепляю солюшн с контролом и его тестовым экзешником.

Автор: AleksPingvin 27.5.2011, 08:53
Век живи, дорогой Mikle! Спасибо тебе огромное! Вечером буду смотреть дома)

Автор: AleksPingvin 27.5.2011, 15:16
Посмотрел, все работает) Жаль правда нельзя сделать так что бы не ждать полного пробега "хвоста", но это я попытаюсь заказчика убедить, что он не прав....

Автор: -Mikle- 27.5.2011, 15:40
Если я правильно тебя понял, то вот тут:
Код

                            <Storyboard x:Key="marqueeStoryboard" Storyboard.TargetName="CONTENT_PART" Storyboard.TargetProperty="(Canvas.Left)">
                                <DoubleAnimation From="{Binding StartContentAtRight, RelativeSource={RelativeSource TemplatedParent}}"
                                                 To="{Binding EndContentAtLeft, RelativeSource={RelativeSource TemplatedParent}}"
                                                 Duration="{Binding Duration, RelativeSource={RelativeSource TemplatedParent}}" RepeatBehavior="Forever"/>
                            </Storyboard>

в анимации есть привязка То к EndContentAtLeft (конец контента слева). Есть еще свойство EndContentAtRight (конец контента справа).

Добавлено через 3 минуты и 27 секунд
Если имеется ввиду что сразу после конца контента располагается его начало, то да, в текущей реализации нельзя, так как контент один. Но тебе никто не мешает сделать компонент, который рисует контент через вижуалбраш smile

Автор: AleksPingvin 27.5.2011, 17:13
Цитата

Если имеется ввиду что сразу после конца контента располагается его начало, то да, в текущей реализации нельзя, так как контент один. Но тебе никто не мешает сделать компонент, который рисует контент через вижуалбраш

Именно это я и имел ввиду.. а про VisualBrush... это идея! Я ведь ей на Adorner-е рисую... почему и тут не заюзать)

Автор: -Mikle- 27.5.2011, 20:33
Да, можно попробовать поставить ей повторение и анимировать трансформацией, примененной к ней.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)