Главная » Silverlight » Поддержка поведений

0

і

Инфраструктура повторного использования кода пользовательского интерфейса включена не в Silverlight SDK, а в Expression Blend 3. Это объясняется тем, что поведе­ния были задуманы как средство времени разработки в Expression Blend. В настоящее время Expression Blend — единственный инструмент, позволяющий добавлять поведения путем их перетаскивания на элементы управления, к которым их нужно подключить. Это не означает, что поведения полезны только в Expression Blend. Вы можете создавать и применять их в Visual Studio, для этого нужно лишь потратить немного больше време­ни (вместо перетаскивания из окна инструментов придется писать разметку вручную).

Существует два способа получения сборок, необходимых для реализации поведений.

•          Установите программу Expression Blend 3 (или ее бесплатную оценочную версию, доступную по адресу www.microsoft.com/expression/try-it/Default.aspx).

•          Установите пакет Expression Blend 3 SDK, доступный по адресу www. tinyurl. com/ kkp4g8.

После выполнения любой из этих операций вы найдете две важные сборки в папке

C:\Program Files\Microsoft SDKs\Expression\Blend 3\Interactivity\Libraries\ Silverlight. Это следующие сборки.

•          System.Windows. Interactivity.dll. Эта сборка является основной и содержит определения базовых классов, предназначенных для поддержки поведений.

•          Microsoft.Expression.Interactions.dll. Эта сборка содержит ряд полезных дополнений, включая необязательные классы действий и триггеров, основанные на базовых классах поведений.

В следующих разделах сначала рассматривается разработка простых поведений на основе сборки System. Windows. Interactivity. dll, а затем — испо""льзование сборки Microsoft. Expression. Interactions . dll для включения в приложение готовых слож­ных поведений.

Триггеры и действия

Поведения состоят из трех компонентов: триггеров, действий и классов поведений. Классы поведений часто называют "поведениями".

Триггер и действие работают совместно: триггер срабатывает, когда что-либо про­исходит, и запускает действие. Вместе они реализуют простейшую форму поведения.

Создание действий

Понять взаимодействие триггеров и действий легче всего, создав простое действие. Предположим, необходимо воспроизвести звук, когда пользователь выполняет некото­рую операцию, например щелкает на кнопке. Это несложно сделать и без поведений. Достаточно добавить на страницу объект MediaElement, предоставить ему URI аудиофай­ла и вызвать метод MediaElement. Play () в обработчике щелчка на кнопке. Однако все эти подробности вносят в приложение лишнюю путаницу. Если нужно воспроизводить различные звуки в ответ на разные события, вам придется создать довольно много кода.

Избежать этого можно с помощью действия, отвечающего за воспроизведе­ние. Сначала создайте сборку библиотечных классов (в данном примере она назы­вается CustomBehaviorsLibrary). Затем добавьте ссылку на сборку System. Windows . Interactivity. dll. И наконец, создайте класс действия, производный от TriggerAction.

public class PlaySoundAction :

TriggerAction<FrameworkElement>

{ … >

Все действия наследуют класс TriggerAction, имя которого может сбить-с толку. Класс назван так потому, что производные от него действия запускаются триггерами. Теоретически возможна модель, в которой действия запускаются иными способами, од­нако в настоящее время для этого нет причин, и первая половина названия оказалась лишней. Однако название существует, и его надо использовать.

Примечание. Вам редко придется создавать действия самому. Вместо этого, вы можете использовать готовые действия, созданные другими. Хотя сборка System.Windows . Interactivity.dll не содержит готовых классов действий, вы найдете их во многих местах, включая дальнейшие разделы данной главы.

Из приведенной выше разметки видно, что в классе TriggerAction используется обобщение. При создании класса, производного от TriggerAction, нужно предоставить тип элемента, который будет использоваться в действии в качестве типа параметра. Обычно используется тип UIElement или FrameworkElement (если создаваемое действие не требует какой-либо особенной функциональности). В данном примере действие PlaySoundAction поддерживает любой элемент, производный от FrameworkElement. Он выбран вместо более универсального класса UIElement потому, что для действия необхо­дим класс VisualTreeHelper. Однако, поскольку все элементы наследуют класс Frame­workElement, который в свою очередь наследует UIElement, существенной разницы нет.

Как и любому классу, действию необходимы свойства. В практических задачах ре­комендуется использовать зависимые свойства, лучше совместимые со средствами Silverlight, однако в данном примере действию PlaySoundAction необходимо только одно свойство — адрес URI, указывающий на аудиофайл.

public static readonly DependencyProperty SourceProperty = DependencyProperty.Register ("Source", typeof(Uri), typeof(PlaySoundAction), new PropertyMetadata (null));

public Uri Source

{

get { return

(Uri)GetValue(PlaySoundAction.SourceProperty); }

set { SetValue(PlaySoundAction.SourceProperty, value); }

}

Когда триггер срабатывает, он активизирует действие путем вызова метода Invoke (). Этот метод необходимо переопределить для добавления кода в действие. В данном при­мере нужно добавить код, воспроизводящий аудиофайл.

Как показано в главе 11, поддержка мультимедиа в Silverlight обладает существен­ным ограничением. Чтобы воспроизвести что-либо с помощью элемента MediaElement, нужно разместить его в иерархии элементов. И даже если MediaElement должен вос­произвести единственный звук, он все равно должен быть размещен в визуальном де­реве страницы.

Существует несколько способов преодоления этого ограничения в классе PlaySoundAction. Например, можно включить в него свойство, принимающее имя эле­мента MediaElement, расположенного на странице. Тогда объект PlaySoundAction смо­жет найти MediaElement и применить его. Другой способ состоит в создании объек­та MediaElement (при необходимости) и удалении его, когда воспроизведение заверше­но. Такой способ имеет ряд преимуществ: он избавляет от необходимости самому опре­делять объект MediaElement и позволяет одновременно воспроизводить неограничен­ное количество звуков. Ниже приведен код, решающий эту задачу с помощью метода FindContainer (), который находит контейнер для размещения объекта MediaElement.

protected override void Invoke(object args)

{

// Поиск места для вставки объекта MediaElement.

Panel container = FindContainer ();

if (container != null)

{

// Создание и конфигурирование объекта MediaElement. MediaElement media = new MediaElement (); media.Source = this.Source;

// Подключение обработчика, который очистит // контейнер по завершении воспроизведения

media.MediaEnded += delegate {

container.Children.Remove(media);

} ;

media.MediaFailed += delegate {

container.Children.Remove(media);

};

// Добавление MediaElement и запуск воспроизведения media. AutoPlay = true; container.Children.Add(media);

}

)

Метод FindContainer () использует класс VisualTreeHelper для прохода по иерар­хии элементов, начиная с текущего элемента. Он останавливается, как только находит любую панель (т.е. объект, производный от Panel), в которую можно вставить объект

MediaElement. Если панель не найдена, значит, объект MediaElement нельзя добавить и аудиофайл не будет воспроизведен. Это может произойти только в окне, содержа­щем единственный элемент управления. Для получения текущего элемента, к кото­рому подключается действие, используется унаследованное свойство TriggerAction. AssociatedObject.

private Panel FindContainer()

{

FrameworkElement element = this.AssociatedObject;

// Поиск панели, на которую можно

// вставить объект MediaElement

while (element != null)

{

if (element is Panel) return (Panel)element;

element = VisualTreeHelper.GetParent(element) as FrameworkElement;

}

return null;

}

Это весь код, необходимый для действия. Как видите, класс действия состоит глав­ным образом из нескольких свойств и метода Invoke ().

Подключение действия к элементу

Чтобы применить действие, необходим триггер. Триггер связан с элементом, а действие — с триггером. Это означает, что первый этап использования действия PlaySoundAction состоит в выборе подходящего триггера.

Все триггеры наследуют класс TriggerBase. Сборка System.Windows. Interactivity, dll содержит единственный триггер EventTrigger, который срабатывает при возник­новении специфического события. Вы можете создать собственные триггеры, однако класс EventTrigger достаточно гибкий для использования почти в любых ситуациях.

Примечание, Одной из причин создания собственного триггера может быть необходимость реагирования на определенную комбинацию событий и состояний. Например, пользовательский триггер может перехватить событие, проверить ряд параметров и решить, нужно ли генерировать срабатывание. Создать такой триггер несложно: необходимо унаследовать класс TriggerBase, переопределить метод OnAttachedO , чтобы подключить соответствующее событие, и метод OnDetaching (), чтобы отключить обработчик события. Когда событие возникнет, его обработает триггер; обработчик больше не нужен. Сработав, триггер вызывает унаследованный метод InvokeActions ().

Чтобы протестировать действие PlaySoundAction, создайте проект Silverlight. Добавьте ссылку на библиотеку классов, в которой определен класс PlaySoundAction (вы создали ее в предыдущем разделе), и ссылку на сборку System.Windows . Interactivity, dll. Отобразите оба пространства имен. Если класс PlaySoundAction находится в би­блиотеке CustomBegaviorsLibrary, необходима следующая разметка.

<UserControl xmlns:i= "clr-namespace:System.Windows.Interactivity; assembly=System.Windows.Interactivity" xmlns:custom= "clr-namespace:CustomBehaviorsLibrary;XXX assembly=CustomBehaviorsLibrary" . . .>

С помощью подключенной коллекции Interaction.Triggers, определенной в System.Windows.Interactivity.dll, триггер можно подключить к любому элементу. В приведенной ниже разметке триггер добавляется в кнопку.

<Button Content="Click to Play Sound"> <i:Interaction.Triggers> <i:EventTrigger EventName="Click"> </i:EventTrigger> </i:Interaction.Triggers>

</Button>

Свойство EventTrigger. EventName идентифицирует событие, на которое нуж­но отреагировать. В данном примере триггер срабатывает при возникновении собы­тия Button.Click. Окончательный этап состоит в добавлении поведения в коллекцию EventTrigger .Actions. Это можно сделать в разметке, объявив поведение в триггере.

<Button Content="Click to Play Sound"> <i:Interaction.Triggers> <i:EventTrigger EventName="Click">

<custorn:PlaySoundAction Source="test.mp3" />

</i:EventTrigger> </i:Interaction.Triggers>

</Button>

Теперь можно открыть страницу и протестировать кнопку. При щелчке на ней долж­но начаться воспроизведение файла test.mp3. С точки зрения разработчика приложе­ния, для этого нужно лишь определить поведение и добавить несколько строк размет­ки. Это же действие можно использовать для подключения звука к любым элементам страницы при возникновении любых других событий. Можно также добавить в триггер несколько действий, тогда они будут быстро запущены по очереди, одно после другого.

Рабочая среда Expression Blend предоставляет более удобные средства создания по­ведений, вообще избавляющие от необходимости писать разметку.

і

в Expression Blend во время разработки

В рабочей среде Expression Blend поведения создаются путем их перетаскивания и конфигурирования. В первую очередь нужно убедиться в том, что приложение содер­жит ссылку на сборку с необходимыми поведениями (в данном примере — на сборку библиотеки, в которой определен класс PlaySoundAction). Кроме того, в приложении должна быть ссылка на сборку System.Windows . Interactivity.dll.

Программа Expression Blend автоматически ищет все нужные сборки и отображает их на панели Asset Library (Библиотека компонентов). Эта же панель используется для выбора элементов при создании страницы Silverlight. Программа извлекает поведения из сборки Microsoft.Expression.Interactions.dll, даже если в проекте еще нет ссы­лок на них.

Чтобы увидеть список поведений, из которых можно выбрать нужные, создайте кнопку на рабочей поверхности страницы, щелкните на кнопке Asset Library и откройте категорию Behaviors (Поведения), показанную на рис. 12.2.

Чтобы добавить действие в элемент управления, перетащите его с панели Asset Library на элемент (в данном примере — на кнопку). Программа автоматически соз­даст для элемента триггер и действие. Однако оба объекта вы должны сконфигуриро­вать сами. Обычно это можно сделать, выбрав действие на панели Objects and Timeline (Объекты и расписание) и настроив его параметры в окне Properties (Свойства), как по­казано на рис. 12.3.

Рис. 12.2. Действия на панели Asset Library

По умолчанию при добавлении действия в кнопку Expression Blend создает для со­бытия Click объект EventTrigger. Событие или другой триггер можно выбрать в окне Properties (см. рис. 12.3). В других элементах по умолчанию используются другие собы­тия. Например, в ListBox используется событие SelectionChanged, а в Rectangle — со­бытие Loaded.

В Expression Blend можно задать, какой триггер будет установлен по умолчанию. Для этого нужно добавить атрибут DefaultTrigger в класс события. Ниже приведен код, приказывающий программе применить триггер EventTrigger, реагирующий на со­бытие MouseLeftButtonDown.

[DefaultTrigger(typeof (UIElement), typeof(EventTrigger), new object[] {"MouseLeftButtonDown"})] public class PlaySoundAction :

TriggerAction<FrameworkElement>

{ ??• }

Рис. 12.3. Конфигурирование действия и его триггера

Атрибут DefaultTrigger принимает несколько параметров, определяющих тип под­ключаемого элемента, тип триггера и дополнительную информацию, передаваемую в конструктор триггера (например, имя события). На основе типа элемента, к которо­му подключается действие, можно выбрать другой триггер. Для этого добавьте несколь­ко атрибутов DefaultTrigger, расположив их в последовательности от более специфич­ного к менее специфичному. Например, приведенная ниже комбинация атрибутов при подключении действия PlaySoundAction к кнопке обеспечивает следующую последова­тельность операций. Сначала Expression Blend создает триггер EventTrigger события Click. Затем для элементов, производных от Shape (таких как Rectangle), программа создает триггер EventTrigger события MouseEnter. Для остальных элементов програм­ма создает набор триггеров EventTrigger события MouseLeftButtonDown.

[DefaultTrigger(typeof(ButtonBase), typeof(EventTrigger),

new object[] ("Click"})] [DefaultTrigger(typeof(Shape), typeof(EventTrigger),

newobject[] ("MouseEnter"})] [DefaultTrigger(typeof(UIElement), typeof(EventTrigger),

newobject[] ("MouseLeftButtonDown"})] public class PlaySoundAction :

TriggerAction<FrameworkElement>

{ … )

Создание направленного триггера

Каждое действие может получить доступ к элементу, к которому оно подключено, с помощью свойства TriggerAction.AssociatedObject. В PlaySoundAction это позво­ляет коду проходить по дереву элементов в поиске подходящего контейнера. Другие действия могут извлекать дополнительную информацию из элемента или изменять его заданным образом. Однако многим действиям нужно пройти дальше подключенного элемента и выполнить заданные операции над другим элементом. Например, иногда щелчок на кнопке должен привести к изменению элемента управления, расположенного в другом месте. Указанную задачу можно решить, добавив соответствующие свойства, однако Silverlight предоставляет более простое решение: действие может наследовать специальный класс TargetedTriggerAction. Он предоставляет свойство Target, которое может быть считано кодом триггера. Затем код может выполнить заданную операцию над элементом, указанным в свойстве Target. В остальном TargetedTriggerAction ни­чем не отличается от стандартного TriggerAction.

В приведенном ниже примере используются два действия, производные от TargetedTriggerAction. Первое действие (FadeOutAction) запускает в целевом элемен­те анимацию, которая плавно уменьшает свойство Opacity до нуля, скрыв таким обра­зом элемент. Второе действие (FadelnAction) применяет другую анимацию для плавной прорисовки элемента на экране. Ниже приведен полный код элемента FadeOutAction.

public class FadeOutAction :

TargetedTriggerAction<UIElement>

{

// Установка времени затенения равным 2 секундам public static readonly DependencyProperty

DurationProperty = DependencyProperty.Register ( "Duration", typeof(TimeSpan), typeof(FadeOutAction), new PropertyMetadata(TimeSpan.FromSeconds(2)));

public TimeSpan Duration {

get { return (TimeSpan)GetValue(

FadeOutAction.DurationProperty); }

set { SetValue(FadeOutAction.DurationProperty,

value); }

}

private Storyboard fadeStoryboard = new Storyboard(); private DoubleAnimation fadeAnimation =

new DoubleAnimation ();

public FadeOutAction() {

fadeStoryboard.Children.Add(fadeAnimation);

}

protected override void Invoke(object args) {

II Проверка, не запущена ли уже раскадровка

fadeStoryboard.Stop();

// Инициализация раскадровки

Storyboard.SetTarget(fadeAnimation, this.Target);

Storyboard.SetTargetProperty(fadeAnimation, new PropertyPath("Opacity"));

// Установка анимации. Эту операцию необходимо

// выполнить в самый последний момент на случай, // если значение Duration изменится fadeAnimation.То = 0; fadeAnimation.Duration = Duration;

fadeStoryboard.Begin ();

}

}

Действие FadelnAction почти идентичное. Оно увеличивает свойство Opacity до единицы. По умолчанию время анимации равно 0,5 секунды.

public class FadelnAction : TargetedTriggerAction<UIElement> {

//По умолчанию время анимации равно 0,5 секунды public static readonly DependencyProperty

DurationProperty = DependencyProperty.Register( "Duration", typeof(TimeSpan), typeof(FadelnAction), new PropertyMetadata! TimeSpan.FromSeconds(0.5)));

public TimeSpan Duration (

get ( return (TimeSpan)

etValue(FadelnAction.DurationProperty); } set ( SetValue(FadelnAction.DurationProperty, value); }

)

private Storyboard fadeStoryboard = new Storyboard(); private DoubleAnimation fadeAnimation =

new DoubleAnimation ();

public FadelnAction() f

fadeStoryboard.Children.Add(fadeAnimation);

}

protected override void Invoke(object args) {

// Проверка, не запущена ли уже раскадровка fadeStoryboard.Stop();

// Инициализация раскадровки

Storyboard.SetTarget(fadeAnimation, this.Target); Storyboard.SetTargetProperty(fadeAnimation, new

PropertyPath("Opacity")); // Установка анимации fadeAnimation.To = 1; fadeAnimation. Duration = Duration;

fadeStoryboard.Begin();

)

}

В приведенном ниже примере используются оба действия. Щелкнув на одной из двух кнопок, пользователь может плавно скрыть или отобразить текстовый блок.

<StackPanel Orientation="Horizontal" Margin="3,15"> <Button Content="II^KHHTe, чтобы затенить надпись" Padding="5">

<i:Interaction.Triggers> <i:EventTrigger EventName="Click">

<custom:FadeOutAction TargetName="border" /> </i:EventTrigger> </i:Interaction.Triggers> </Button>

<Button Content="Il^KHMTe, чтобы вывести надпись" Padding="5"> <i:Interaction.Triggers> <i:EventTrigger EventName="Click">

<custom:FadelnAction TargetName="border" /> </i:EventTrigger> </i:Interaction.Triggers> </Button> </StackPanel>

<Border x:Name="border" Background="Orange"

BorderBrush="Black" BorderThickness="l" Margin="3,0" > <TextBlock Margin="5" FontSize="17" TextWrapping="Wrap" Text="3aTeHHeMaH надпись"></TextBlock>

</Border>

Результат показан на рис. 12.4.

Рис. 12.4. Использование направленного триггера

Действия FadeOutAction и FadelnAction можно объединить в одном обобщенном действии FadeAction, имеющем дополнительное свойство TargetOpacity. Однако пове­дения обычно создаются специфичными, чтобы их можно было присваивать элемен­ту, не засоряя разметку дополнительными свойствами. Аналогично анимацию можно выполнять с помощью действия ControlStoryboardAction, предопределенного в сбор­ке Microsoft.Expression. Interactivity.dll, однако лучше разработать специфичный шаблон действия, инкапсулирующий анимацию и предоставляющий уместные высоко­уровневые свойства. Это упрощает приложение и уменьшает объем разметки.

Направленные триггеры позволяют изменить действие PlaySoundAction та­ким образом, чтобы избежать создания и удаления объектов MediaElement (конеч­но, придется ограничиться одним звуком). Для этого вместо динамического созда­ния объекта MediaElement создайте его в разметке и задайте наследование класса TargetedTriggerAction действием PlaySoundAction.

Элемент-источник и целевой элемент можно поменять местами

Среди разработчиков этот факт мало известен. Целевой элемент задается с помощью свойства TargetName, при­надлежащего действию, а элемент-источник — с помощью свойства SourceName, принадлежащего триггеру. Это означает, что источником действия не обязательно должен быть элемент, в котором оно расположено. Возможны два разных сценария. Чаще используется тот, с которым вы уже знакомы, — триггер и действие вложены в элемент-источник. Это называется моделью приказов (tell), потому что вы приказываете действию обработать за­данный целевой объект. Большую гибкость обеспечивает модель прослушивания (listen), в которой действие опреде­лено в целевом объекте и вы просите его прослушивать другой элемент-источник. В обеих моделях действие ра­ботает одинаково. Рабочая среда Expression Blend поддерживает оба сценария. Теоретически можно также создать действие в первом элементе, установить источник во второй элемент и указать в качестве целевого третий элемент, однако тяжело представить себе ситуацию, в которой такой сценарий имеет преимущества.

Создание поведения

Во многих местах действия представлены как поведения. В частности, в окне Asset Library (Библиотека компонентов) программы Expression Blend действия приведены в разделе Behaviors (Поведения). Однако есть и другой тип поведений — поведения, представленные классом Behavior.

Поведения, как и действия, предназначены для инкапсуляции функционально­сти пользовательского интерфейса, поэтому их можно применять к элементам, не на­писав ни единой строки кода. Каждое действие представляет собой отдельную часть кода, решающего одну задачу. Можно создать набор связанных действий (например, FadeOutAction и FadelnAction), однако каждое действие самодостаточно и может ис­пользоваться независимо от других. Действия и триггеры взаимно дополняют друг дру­га, а отдельные действия не связаны со специфическими триггерами. Любое действие может быть использовано в любом триггере.

В то же время поведения группируют комбинации связанных операций. Они управ­ляют работой триггеров (прослушиванием определенных событий и обнаружением из­менений) и действий (выполнением определенных операций). Обычно поведения реша­ют связанные задачи, которые нельзя разбить на меньшие блоки, потому что они всег­да используются в сочетании друг с другом. Например, инфраструктура глубокого зуми­рования состоит из нескольких обработчиков событий, позволяющих панорамировать и зумировать изображение с помощью кнопок и колесика мыши. Вместе обработчики составляют базовый уровень поддержки глубокого зумирования, поэтому они объедине­ны в одно поведение DeepZoomBehavior.

Совет. Если действия совместно используют некоторые данные или взаимодействуют друг с другом (или нужно установить жесткую связь между действием и поведением), рекомендуется реализовать функциональность приложения как поведение.

Чтобы лучше понять принципы работы поведений, создайте одно поведение сами. Предположим, нужно предоставить пользователю возможность перетаскивать любой элемент на панели Canvas с помощью мыши. В главе 4 рассмотрено простое решение этой задачи для эллипсов. Приложив немного больше усилий, вы можете преобразовать код, приведенный в главе 4, в повторно используемое поведение, предоставляющее воз­можность перетаскивать любой элемент в любом объекте Canvas.

Первый этап — создание класса, производного от Behavior. Как и классы TriggerAction и TargetedTriggerAction, класс Behavior является обобщенным, т.е. принимающим тип в качестве аргумента. Передаваемый тип можно использовать для

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

UIElement или FrameworkElement.

public class DraglnCanvasBehavior : Behavior<UIElement> { … }

Необходимо также переопределить методы OnAttachedO и OnDetaching (). При вы­зове OnAttached () вы получаете доступ (посредством свойства AssociatedObject) к эле­менту, в котором размещено поведение, и можете подключить обработчики событий. При вызове метода OnDetaching () можно удалить обработчики событий.

Приведенный ниже код используется поведением DraglnCanvasBehavior для управ­ления событиями MouseLeftButtonDown, MouseMove и MouseLeftButtonUp.

protected override void OnAttachedO {

base.OnAttached();

// Подключение обработчиков событий

this.AssociatedObject.MouseLeftButtonDown +=

As sociatedObj ect_MouseLe ftButtonDown; this.AssociatedObject.MouseMove +=

AssociatedObject_MouseMove; this.AssociatedObj ect.MouseLeftButtonUp += AssociatedObject_MouseLeftButtonUp;

}

protected override void OnDetaching()

{

base.OnDetaching();

// Отключение обработчиков событий

this.AssociatedObject.MouseLeftButtonDown -=

AssociatedObject_MouseLeftButtonDown; this.AssociatedObject.MouseMove -=

AssociatedObject_MouseMove; this.AssociatedObj ect.MouseLeftButtonUp -= AssociatedObject_MouseLeftButtonUp;

}

Окончательный этап создания поведения — запуск кода в обработчиках со­бытий. Например, когда пользователь нажимает левую кнопку мыши, поведение DraglnVanvasBehavior начинает операцию перетаскивания, записывает расстояние между указателем и левым верхним углом элемента и захватывает мышь.

// Холст, на котором расположен элемент private Canvas canvas;

// Отслеживание режима перетаскивания private bool isDragging = false;

// Запись позиции щелчка private Point mouseOffset;

private void AssociatedObjeCt_MouseLeftButtonDown (object sender, MouseButtonEventArgs e)

// Извлечение холста if (canvas == null)

canvas = (Canvas)VisualTreeHelper.GetParent(this.AssociatedObject);

// Началось перетаскивание isDragging = true;

// Получение позиции щелчка относительно элемента // (левый верхний угол элемента имеет координаты 0,0) mouseOffset = е.GetPosition(AssociatedObject);

// Захват мыши; благодаря захвату код будет получать // событие MouseMove, даже если рука вздрогнет и // указатель выпрыгнет за пределы элемента AssociatedObject.CaptureMouse ();

}

Когда указатель перемещается в режиме перетаскивания, позиция элемента непре­рывно обновляется.

private void AssociatedObject_MouseMove(object sender,

MouseEventArgs e)

{

if (isDragging) {

// Получение позиции элемента относительно холста Point point = е.GetPosition(canvas);

// Перемещение элемента

AssociatedObj ect.SetValue(Canvas.TopProperty, point.Y – mouseOffset.Y); AssociatedObject.SetValue(Canvas.LeftProperty, point.X – mouseOffset.X);

}

}

В момент отпускания кнопки мыши перетаскивание завершается.

private void AssociatedObject_MouseLeftButtonUp( object sender, MouseButtonEventArgs e)

{

if (isDragging) {

AssociatedObject.ReleaseMouseCapture (); isDragging = false;

}

)

Чтобы применить поведение, его достаточно добавить в любой элемент, расположен­ный в контейнере Canvas. Приведенная ниже разметка создает контейнер Canvas с тре­мя фигурами. Два элемента Ellipse поддерживают поведение DraglnCanvasBehavior, поэтому их можно перетаскивать. К элементу Rectangle поведение не подключено, по­этому перетаскивать его нельзя.

<Canvas>

<Rectangle Canvas.Left="10" Canvas.Top="10" Fill="Yellow"

Width="40" Height="60"> </Rectangle>

<Ellipse Canvas.Left="10" Canvas.Top="70" Fill="Blue" Width="80" Height="60">

<i:Interaction.Behaviors> <custorn:DraglnCanvasBehavior» </custom:DraglnCanvasBehavior»

</i:Interaction.Behaviors» </Ellipse>

<Ellipse Canvas.Left="80" Canvas.Top="70" Fill="OrangeRed" Width="40" Height="70"> <i: Interaction.Behaviors» <custom:DraglnCanvasBehavior» </custom:DraglnCanvasBehavior» </i:Interaction.Behaviors» </Ellipse> </Canvas>

Результат показан на рис. 12.5.

Рис. 12.5. Поведение делает элементы перетаскиваемыми

Источник: Мак-Дональд, Мэтью. Silverlight 3 с примерами на С# для профессионалов. : Пер. с англ. —- М. : ООО «И.Д. Вильяме», 2010. — 656 с. : ил. — Парал. тит. англ.

По теме:

  • Комментарии