• Nie Znaleziono Wyników

Programowanie w technologii .NET wykład 3 – Dependency Properties, Routed Events

N/A
N/A
Protected

Academic year: 2021

Share "Programowanie w technologii .NET wykład 3 – Dependency Properties, Routed Events"

Copied!
29
0
0

Pełen tekst

(1)

Programowanie w technologii .NET

wykład 3 – Dependency Properties, Routed Events

Dependency Properties – własności zależnościowe - wydajniejsze pamięciowo

- dziedziczenie wartości (w drzewie elementów) - powiadomienia o zmianie wartości

- potrzebne do stylów, animacji, wiązania danych - używa się ich tak samo, jak zwykłych własności

(2)

klasyczne własności:

class FrameworkElement {

Thickness Margin {

set { ... = value; } get { return ...; } }

}

a jak są definiowane Dependency Properties?

najpierw statyczna składowa reprezentująca definiowaną własność:

public class FrameworkElement : UIElement, ...

{

public static readonly DependencyProperty MarginProperty;

// ...

}

(3)

rejestrowanie właściwości w statycznym konstruktorze:

static FrameworkElement() {

FrameworkPropertyMetadata metadata =

new FrameworkPropertyMetadata(new Thickness(), FrameworkPropertyMetadataOptions.AffectsMeasure);

MarginProperty = DependencyProperty.Register("Margin", typeof(Thickness), typeof(FrameworkElement),

metadata, IsMarginValid);

//...

}

walidacja (uwaga: to metoda statyczna, zatem sprawdza tylko podaną wartość):

private static bool IsMarginValid(object value) {

Thickness thickness1 = (Thickness)value;

if(...)

return true;

return false;

}

(4)

wrapper (te metody są wołane tylko z kodu C#, a nie XAMLa):

public Thickness Margin {

set { SetValue(MarginProperty, value); }

get { return (Thickness)GetValue(MarginProperty); } }

teraz mamy gotową właściwość:

myElement.Margin = new Thickness(5);

jest też dostępne czyszczenie lokalnie ustawionej wartości:

myElement.ClearValue(FrameworkElement.MarginProperty);

Property Metadata

pozwala na ustawienie kilku dodatkowych cech definiowanej własności najważniejsze:

DefaultValue – domyślna wartość własności

CoerceValueCallback – testowanie zgodności wartości

PropertyChangedCallback – powiadomienie o zmianie wartości

(5)

Coercion

1. CoerceValueCallback ma szansę na zmianę dostarczonej wartości albo ją odrzucić 2. ValidateValueCallback sprawdza poprawność wartości (statycznie!)

3. PropertyChangedCallback – gdy zmiana zaszła pomyślnie przykład koercji na scrollu i właściwości Maximum:

private static object CoerceMaximum(DependencyObject d, object value) { RangeBase base1 = (RangeBase)d;

if (((double)value) < base1.Minimum) {

return base1.Minimum;

}

return value;

}

(6)

podobnie dla Value:

internal static object ConstrainToRange(DependencyObject d, object value) { double newValue = (double)value;

RangeBase base1 = (RangeBase)d;

double minimum = base1.Minimum;

if (newValue < minimum) {

return minimum;

}

double maximum = base1.Maximum;

if (newValue > maximum) {

return maximum;

}

return newValue;

}

(7)

w Minimum nie ma koercji, ale jest odpalenie zmiany pozostałych, gdy trzeba:

private static void OnMinimumChanged(DependencyObject d,

DependencyPropertyChangedEventArgs e) { RangeBase base1 = (RangeBase)d;

// ...

base1.CoerceValue(RangeBase.MaximumProperty);

base1.CoerceValue(RangeBase.ValueProperty);

}

podobnie Maximum wymusza koercję Value:

private static void OnMaximumChanged(DependencyObject d,

DependencyPropertyChangedEventArgs e) { RangeBase base1 = (RangeBase)d;

//...

base1.CoerceValue(RangeBase.ValueProperty);

}

Ma to zadbać o właściwe dopasowanie wartości:

ScrollBar bar = new ScrollBar(); // Value = 0, Minimum = 0, Maximum = 1 bar.Value = 100; // Value = 1 (koercja)

bar.Minimum = 1; // Value = 1

bar.Maximum = 200; // znow odpalona koercja Value i Value = 100 (*)

(*) - koercja odpalona z oryginalnie podaną wartością właściwości – zatem ustalone jest Value = 100 (!)

(8)

Shared Dependency Properties

Niekiedy kilka klas (w osobnych hierarchiach) korzysta z tej samej własności, np.

TextBlock.FontFamily i Control.FontFamily wskazują na tę samą własność zdefiniowaną w klasie TextElement; robi się to wywołując AddOwner:

TextBlock.FontFamilyProperty =

TextElement.FontFamilyProperty.AddOwner(typeof(TextBlock));

Attached Dependency Properties

Dotyczą innego elementu niż są zdefiniowane. Np. Grid.Row zdefiniowane jest w Gridzie, a dotyczy elementu w nim osadzonego.

Rejestruje się je przy użyciu RegisterAttached:

FrameworkPropertyMetadata metadata = new FrameworkPropertyMetadata(

0, new PropertyChangedCallback(Grid.OnCellAttachedPropertyChanged));

Grid.RowProperty = DependencyProperty.RegisterAttached("Row", typeof(int),

typeof(Grid), metadata, new ValidateValueCallback(Grid.IsIntValueNotNegative));

(9)

Nie definiuje się dla nich wrappera, gdyż mogą być ustawione dla dowolnego elementu.

Zamiast tego korzystamy ze statycznych metod:

public static int GetRow(UIElement element) {

if (element == null) {

throw new ArgumentNullException(/*...*/);

}

return (int)element.GetValue(Grid.RowProperty);

}

public static void SetRow(UIElement element, int value) {

if (element == null) {

throw new ArgumentNullException(/*...*/);

}

element.SetValue(Grid.RowProperty, value);

}

A tak z tego korzystamy:

Grid.SetRow(txtElement, 0);

co przekłada się na:

txtElement.SetValue(Grid.RowProperty, 0);

(10)

Jak używane są własności zależnościowe?

(11)

Gdy zmieni się wartość własności, uruchamiana jest metoda callbackowa

PropertyChangedCallback – nie odpala ona jednak domyślnie eventów. Zamiast tego powiadamiane są data-bindingi i triggery (będzie o nich mowa w następnych rozdziałach).

Jedynie część własności uruchamia jakieś powiązane z ich zmiana zdarzenia (np.

TextBox.TextChanged, ScrollBar.ValueChanged).

Gdy pobieramy wartość własności zależnościowej, WPF poszukuje jej w:

1. domyślnej wartości 2. wartości odziedziczonej 3. wartości podanej w stylu

4. wartości wpisanej lokalnie (w kodzie lub XAMLu)

Tak pobrana wartość, zanim zostanie zwrócona, może być modyfikowana przez wyrażenia, wiązanie danych, dołączone animacje, koercje.

(12)

Zdarzenia w WPF

Event Routing

Routed Events – podróżują po drzewie elementów.

(13)

rodzaje zdarzeń:

- direct – bezpośrednie (dotyczą tylko jednego elementu)

- bubbling (wędrują w górę hierarchii zagnieżdżenia – najpierw podnoszone przez element którego dotyczą)

- tunneling (wędrują w dół hierarchii zagnieżdżenia – najpierw podnoszone przez element najwyższego poziomu – okno)

przekazany do obsługi argument typu RoutedEventArgs zawiera własność Handled – pozwala przerwać tunelowanie/ bąbelkowanie

private void DoSomething(object sender, RoutedEventArgs e) {

if (...) {

e.Handled = true;

} }

RoutedEventArgs.Source – od kogo pochodzi zdarzenie (przeważnie kontrolka) sender – kto je przysłał (gdzie umieszczono obsługę zdarzenia)

RoutedEventArgs.RoutedEvent – zdarzenie

(14)

Attached Events

Wykonawca zdarzenia może być podpięty na poziomie elementu, który podnosi zdarzenie albo do innego elementu powyżej lub poniżej hierarchii zagnieżdżenia:

<StackPanel Button.Click="DoSomething" Margin="5">

<Button Name="btn1" Tag="jeden">Przycisk 1</Button>

<Button Name="btn2" Tag="dwa">Przycisk 2</Button>

<Button Name="btn3" Tag="trzy">Przycisk 3</Button>

...

</StackPanel>

private void DoSomething(object sender, RoutedEventArgs e) {

object tag = ((FrameworkElement)e.Source).Tag;

MessageBox.Show((string)tag);

}

Tunneling Events

Tunneling i Bubbling występują w parach (tunneling ma przeważnie przedrostek Preview) – najpierw wędruje tunneling, a potem bubbling

(15)

<Label BorderBrush="Black" BorderThickness="1">

<StackPanel>

<TextBlock Margin="3">Tekst i ikona</TextBlock>

<Image Source="ikona.jpg" Stretch="None" />

<TextBlock Margin="3">Podpis</TextBlock>

</StackPanel>

</Label>

Label PreviewMouseDown StackPanel PreviewMouseDown Image PreviewMouseDown Image MouseDown

StackPanel MouseDown Label MouseDown Label PreviewMouseUp StackPanel PreviewMouseUp Image PreviewMouseUp Image MouseUp

StackPanel MouseUp Label MouseUp

(16)

Definiowanie zdarzeń w WFP:

public abstract class ButtonBase : ContentControl, ... { // definicja

public static readonly RoutedEvent ClickEvent;

// rejestracja

static ButtonBase() {

ButtonBase.ClickEvent = EventManager.RegisterRoutedEvent(

"Click", RoutingStrategy.Bubble,

typeof(RoutedEventHandler), typeof(ButtonBase));

//...

}

// wrapper

public event RoutedEventHandler Click {

add { base.AddHandler(ButtonBase.ClickEvent, value); } remove {

base.RemoveHandler(ButtonBase.ClickEvent, value); } }

//...

}

(17)

posługiwanie się zdarzeniami:

dołączanie obsługi zdarzenia:

<Button Name="btn1" Click="klik">OK</Button>

w kodzie:

btn1.Click += klik;

i odłączanie

btn1.Click -= klik;

lub:

btn1.AddHandler(ButtonBase.ClickEvent, new RoutedEventHandler(klik));

i

btn1.RemoveHandler(ButtonBase.ClickEvent, new RoutedEventHandler(klik));

nie powinniśmy dołączać w ten sposób wysokopoziomowych (logicznych) metod, a tylko

„event handlery” - oddelegowujące polecenia do warstwy logiki

(18)

WPF Events

Kategorie zdarzeń:

● czasu życia (gdy element jest ładowany, inicjowany, usuwany)

● zdarzenia myszy (akcje myszy)

● zdarzenia klawiatury (akcje klawiatury)

● zdarzenia stylusa Czasu życia:

Podnoszą je wszystkie elementy, gdy są tworzone bądź zwalniane.

Initialized – gdy utworzono instancję elementu i ustawiono jego właściwości. Inne elementy tego samego okna mogą jeszcze nie istnieć. IsInitialized == true. Jest to zwykłe zdarzenie (nie jest routed).

Loaded – gdy całe okno zostało zainicjowane, dołączono style i wiązanie danych. Tuż przed jego wyświetleniem. IsLoaded == true.

Unloaded – gdy element został zwolniony (usunięto go z okna bądź zamknięto okno).

(19)

kolejność działań:

- tworzona instancja obiektu

- przetwarzane i ustawiane właściwości z XAMLa

- Initialized (gdy tworzymy okno elementy są inicjowane „z dołu do góry” – te głębiej w zagnieżdżeniu są pierwsze)

- ułożenie w kontenerze - Loaded („z góry do dołu”)

- renderowanie (wyświetlenie okna, gdy wszystkie elementy załadowane) Zdarzenia czasu życia dla klasy Window:

SourceInitialized – ustawiane powiązania do HWND (Win32 API)

ContentRendered – gdy zawartość okna wyrenderowana po raz pierwszy (okno wyświetlone i gotowe do przyjmowania wejścia)

Activated – gdy nastąpiło przełączenie do okna (albo załadowane po raz pierwszy) – jest to odpowiednik GetFocus kontrolek

Deactivated – użytkownik przełączył się na inne okno (lub zamknął to) – odpowiednik LostFocus

Closing – okno się zamyka (można to anulować – CancelEventArgs.Cancel na true); nie ma Closing, jeśli to system się wyłącza

Closed – okno zostało zamknięte (ale do jego elementów wciąż mamy jeszcze dostęp) Typowe miejsce dla inicjacji kontrolek – Loaded

(20)

Zdarzenia wejścia:

Wszystkie dołączają dwie właściwości: Timestamp (czas zdarzenia w milisekundach) i Device (urządzenie, które odpaliło zdarzenie).

Zdarzenia klawiatury:

naciśnięcie klawisza:

PreviewKeyDown (Tunneling) KeyDown (Bubbling)

wpisanie znaku (odpalają je tylko te klawisze, które powodują wpisanie tekstu):

PreviewTextInput (Tunneling) TextInput (Bubbling)

zwolnienie klawisza:

PreviewKeyUp (Tunneling) KeyUp (Bubbling)

gdy trzymamy naciśnięty klawisz powtarzane są zarówno oba KeyDown jak i TextInput

(21)

Obsługa klawiszy:

<Window ...

KeyDown="KeyEvent" PreviewKeyDown="KeyEvent"

KeyUp="KeyEvent" PreviewKeyUp="KeyEvent">

<ScrollViewer Name="scroll">

<Label Name="lblInfo"/>

</ScrollViewer>

</Window>

private void KeyEvent(object sender, KeyEventArgs e) {

lblInfo.Content += "Event: " + e.RoutedEvent + " Key: " + e.Key + "\n";

scroll.ScrollToBottom();

}

Uwaga: „x” to „x” niezależnie od shift, alt, etc.

Ale czym innym jest Key.D0, a czym innymi Key.NumPad0.

e.IsRepeat – pozwala sprawdzić, czy ten event to efekt trzymania klawisza:

e.Text – zwraca tekst, jaki ma otrzymać kontrolka (w zdarzeniach typu TextInput) e.KeyStates – informuje o stanie naciśniętego klawisza

(22)

o stanie pozostałych można dowiedzieć się z KeyboardDevice:

if ((e.KeyboardDevice.Modifiers & ModifierKeys.Control) ==

ModifierKeys.Control) {

lblInfo.Content = "You held the Control key.";

}

metody KeyboardDevice:

IsKeyDown() - czy dany klawisz był naciśnięty gdy zaszło zdarzenie IsKeyUp()

IsKeyToggled() - tylko dla Caps Lock, Num Lock, Scroll Lock GetKeyStates() - połączenie KeyDown i KeyToggled

Keyboard – aktualny stan klawiszy:

if (Keyboard.IsKeyDown(Key.LeftShift)) {

lblInfo.Content = "The left Shift is held down.";

}

(23)

PreviewTextInput – dobre miejsce do walidacji tekstu w kontrolce (np. gdy chcemy tylko numeryczne ustawiamy Handled gdy nie to co chcemy)

private void pnl_PreviewTextInput(object sender,

TextCompositionEventArgs e) { short val;

if (!Int16.TryParse(e.Text, out val)) {

// tylko klawisze numeryczne e.Handled = true;

} }

private void pnl_PreviewKeyDown(object sender, KeyEventArgs e) { if (e.Key == Key.Space)

{

// spacja tutaj, bo nie podnosi ona PreviewTextInput e.Handled = true;

} }

(24)

Mysz

MouseEnter – kursor wjeżdża nad element MouseLeave – opuszcza element

nie są routed

Poza nimi: PreviewMouseMove (tunneling) i MouseMove (bubbling) – gdy mysz się porusza.

private void MouseMoved(object sender, MouseEventArgs e) {

Point pt = e.GetPosition(this);

lblInfo.Content =

String.Format("Współrzędne: ({0},{1})", pt.X, pt.Y);

}

można sprawdzić też stan przycisków:

if(e.LeftButton == MouseButtonState.Pressed) ...

(25)

Kliknięcia

MouseLeftButtonDown MouseLeftButtonUp to samo jest dla Right

dla każdego istnieje odpowiednik Preview* (tunelling) MouseWheel i Preview* - obsługa kółka

Przekazany parametr MouseButtonEventArgs ma dodatkowo właściwość ClickCount.

Niektóre kontrolki przejmują te zdarzenia, a dają dodatkowe (np. Click w Buttonie).

(26)

Przechwytywanie myszy

Aby otrzymywać zdarzenia z myszy poza elementem.

Mouse.Capture(element) aby zwolnić:

Mouse.Capture(null)

i jeszcze zdarzenie LostMouseCapture, gdy to stracimy

<Window ...

MouseMove="Canvas_MouseMove"

MouseLeftButtonDown="Canvas_MouseLeftButtonDown"

MouseLeftButtonUp="Canvas_MouseLeftButtonUp">

<Canvas>

<Rectangle Name="box" Width="50" Height="50"

Canvas.Top="50" Canvas.Left="100" Fill="Blue" />

</Canvas>

</Window>

(27)

private void Canvas_MouseMove(object sender, MouseEventArgs e) {

if (e.LeftButton == MouseButtonState.Pressed) {

Point pt = e.GetPosition(this);

box.SetValue(Canvas.TopProperty, pt.Y-25);

box.SetValue(Canvas.LeftProperty, pt.X-25);

} }

private void Canvas_MouseLeftButtonDown(object sender,

MouseButtonEventArgs e) {

Mouse.Capture(this);

}

private void Canvas_MouseLeftButtonUp(object sender,

MouseButtonEventArgs e) {

Mouse.Capture(null);

}

(28)

przydatne do Drag-and-Drop:

1. klikamy i trzymamy przycisk (pewna informacja zapisana i zaczynamy przeciąganie) 2. ruch myszy na inny element, który może przyjąć drop – sygnalizacja kursorem 3. zwolnienie przycisku – zrzucenie danych

<Label MouseDown="lbl1_MouseDown">Źródło</Label>

private void lbl1_MouseDown(object sender,

MouseButtonEventArgs e) {

Label lbl = (Label)sender;

DragDrop.DoDragDrop(lbl, lbl.Content,

DragDropEffects.Copy);

}

odbiorca wymaga ustawienia:

<Label DragEnter="lbl2_DragEnter"

Drop="label2_Drop" AllowDrop="True">Cel</Label>

(29)

przyjęcie zrzutu:

private void lbl2_Drop(object sender, DragEventArgs e) {

((Label)sender).Content =

e.Data.GetData(DataFormats.Text);

}

sprawdzanie czy dane które możemy przyjąć:

private void lbl2_DragEnter(object sender, DragEventArgs e) {

if (e.Data.GetDataPresent(DataFormats.Text)) e.Effects = DragDropEffects.Copy;

else

e.Effects = DragDropEffects.None;

}

Cytaty

Powiązane dokumenty

• W momencie ładowania okna stwórzmy lub załądujmy listę danych i pobierzmy widok:. List&lt;Book&gt; lst =

• Zawartość separatora można zmieniać przy pomocy szablonów – pozwala to definiować własne, nieklikalne elementy menu (nawet jeśli dodamy do menu wrzucimy element nie

• IsColumnWidthFlexible – decyduje, czy kontener może dopasować rozmiar kolumny (jeśli false, użyta jest dokładna wartość ColumnWidth, jeśli true – ColumnWidth to

• IsFilled – jeśli true, wnętrze jest wypełniane przy użyciu pędzla Path.Fill PathFigure to kształt który narysowany jest linią składającą się z wielu

Wymaga stworzenia obiektu animacji, ustawienia własności i odpalenia jej przy pomocy metody BeginAnimation() (zadeklarowanej w interfejsie

• Jeśli chcemy aby szablony były używane w obrębie wielu aplikacji należy stworzyć osobny słownik zasobów (Resource Dictionary) – Solution Explorer -&gt; Add -&gt;. New

• Positions – kolekcja wszystkich punktów siatki (wierzchołków trójkątów). Często jeden punkt jest wierzchołkiem kilku trójkątów, np: sześcian wymaga 12 trójkątów,

Kompilator używa konstruktora kopiującego, gdy program generuje kopię obiektu (np. w sytuacji, gdy obiekt przekazywany jest do funkcji przez wartość). void f(Osoba