projektowanie komponentów wizualnych

47
Projektowanie komponentów wizualnych WOJCIECH

Upload: pgssoftware

Post on 17-Jul-2015

343 views

Category:

Software


0 download

TRANSCRIPT

Projektowanie komponentów wizualnychWOJCIECH

Agenda

Czym są kontrolki niestandardowe?

Case study

Rodzaje komponentów

Elementy WinAPI

Architektura i implementacja prostych kontrolek

Podsumowanie

Kilka faktów

Konkretne rozwiązania

Programowanie (relatywnie) niskopoziomowe

Pomysły są autorskie

Czym są komponenty niestandardowe?

Kontrolki standardowe

Czym są komponenty niestandardowe?

Kontrolki niestandardowe

Od zera? Dlaczego?Ścisłe wymagania (internetowe kontrolki są zwykle generyczne)Problem: budżet (często $1000+ za stanowisko)Brak kontroli nad źródłemProjektowanie architektury (kierunek rozwoju)Alternatywa: internetowe repozytoria lub Open Source

Kiedy nie pisać osobnych kontrolek?

Czy naprawdę jest potrzebna?Gotowa kontrolka już istniejePodobna kontrolka już istniejeSpecjalny wygląd aplikacji (z wyjątkami)

Case study

Case study: SpkToolbar- Brak analogicznego (darmowego) rozwiązania

- Bardziej kontrolowane rozwiązanie niż oryginalny Ribbon dla Delphi

- Obecnie OpenSource na LCCR (Lazarus Code & Components Repository)- Używany w aplikacjach OpenSource (link)

Case study: ProCalc

- Ścisłe powiązanie z architekturą aplikacji

- Renderowanie przy pomocy Direct2D i Direct3D

Case study: ProTranscriber

- Brak darmowego odpowiednika- Wymagana specyficzna funkcjonalność- Powiązanie z architekturą aplikacji

Case study: Virtual treeview

- Brak wymaganej funkcjonalności w standardowym komponencie

- Brak tego typu komponentu w Internecie (tylko naśladujące)

- Dostępny jako Open Source na Codeplex (hasło: Spk.Controls)

Case study: NodeLab

- Bardzo specyficzne wymagania

- Powiązanie z architekturą aplikacji

Rodzaje komponentów

Rodzaje komponentówCustom controls – kontrolki „od zera”User controls – kontrolki kompozytowe

- Reużywalne fragmenty aplikacji- Złożone z innych kontrolek

- Zawierają pewną logikę

- Odcinają od wewnętrznej struktury- Ograniczona funkcjonalność

- Projektowane dla ściśle konkretnego celu

- Implementowane w całości przez programistę

- Zawierają dużo wewnętrznej logiki (związanej z wyświetlaniem i interakcją z użytkownikiem)

Rodzaje komponentówWyjątek - WPF

<UserControl x:Name="rootControl"x:Class="Webnodes.OntologyDesigner.Common.Controls.BackControl"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:vs="clr-namespace:Microsoft.VisualStudio.Shell..."mc:Ignorable="d">

<Button Width="24" Height="24" Command="{Binding ElementName=rootControl, Path=BackCommand}"><Button.Template>

<ControlTemplate TargetType="{x:Type Button}"><Viewbox Stretch="Uniform">

<Canvas Width="16" Height="16"><Path x:Name="Background" Fill="Transparent">

<Path.Data><PathGeometry Figures="M 16 8 A 8 8 ..." FillRule="NonZero"/>

</Path.Data></Path><Path x:Name="Circle" Fill="{DynamicResource GrayIconColor}">

<Path.Data><PathGeometry Figures="M 8 0 C 3.581722..." FillRule="NonZero"/>

</Path.Data></Path><Path x:Name="Arrow" Fill="{DynamicResource GrayIconColor}">

<Path.Data><PathGeometry Figures="m 3 8 4 -4 3 0 ..." FillRule="nonzero"/>

</Path.Data></Path>

</Canvas></Viewbox>

<ControlTemplate.Triggers><Trigger Property="IsMouseOver" Value="True">

<Setter TargetName="Background" Property="Fill"Value="{DynamicResource {x:Static vs:VsBrushes.HighlightKey}}" />

<Setter TargetName="Circle" Property="Fill"Value="Transparent" />

<Setter TargetName="Arrow" Property="Fill"Value="{DynamicResource {x:Static vs:VsBrushes.ButtonFaceKey}}" />

</Trigger></ControlTemplate.Triggers>

</ControlTemplate></Button.Template>

</Button></UserControl>

Komponent kompozytowy

Komponent kompozytowy

Jednolity wygląd aplikacji (i konsekwencje)

Dodatkowa funkcjonalność (walidacja)

Wygodny interface użytkownikaWygodne API

Architektura - API

Odcinamy użytkownika od wewnętrznych komponentów

Publikujemy własności i zdarzenia wynikające z przeznaczenia komponentu

public string Username { get; set; }

public string Password { get; set; }

[Browsable(false)]

public bool UsernameValid { get; }

[Browsable(false)]

public bool PasswordValid { get; }

public event EventHandler UserValidChanged;

public event EventHandler PasswordValidChanged;

Architektura – zdarzeniaChronione, wirtualne triggery zdarzeń

protected virtual void OnUserValidChanged(){

if (UserValidChanged != null)UserValidChanged(this, EventArgs.Empty);

}

private void SetUserValid(bool value){

(...)

if (changed)OnUserValidChanged();

}

public event EventHandler UserValidChanged;

Architektura – zdarzenia- Modyfikacja oryginalnej funkcjonalności (przed, po, zamiast)

- Decyzja o zgłaszaniu zdarzenia

- Ułatwia rozwój kontrolki (zwiększa elastyczność)

protected override void OnResize(EventArgs e){

if (Height != 60)Height = 60;

base.OnResize(e); }

Użycie komponentu

private void CheckCanConfirm(){

bOK.Enabled = pbPassword.UsernameValid && pbPassword.PasswordValid;}

private void HandleValidChanged(object sender, EventArgs e){

CheckCanConfirm();}

public MainForm(){

InitializeComponent();CheckCanConfirm();

}

Komponent niestandardowy

Komponent niestandardowy- Start small: przycisk

- Standardowe zachowanie – obserwacja standardowego przycisku

- Wygląd - TODO

Podstawy WinAPI

(Square Enix)

Podstawy WinAPI

Okno

Okno

Okno

OknoOkno

Okno

Okno

Okno Okno

Okno

Okno

Podstawy WinAPI

Window

OK

Handle

Handle

• Size• Style• Icon, small icon• Cursor• Background• MenuName• Text

Dygresja - WPF

Window

OK

Handle

Podstawy WinAPI - zdarzenia

WindowsUżytkownikMouseMove Kolejka

komunikatów

WM_MouseMove

512

WndProc(implementacja .NET WinForms)

GetMessage()

OnMouseMoveMouseMove

Użytkownik System operacyjny

.NET FrameworkProgram

OnMouseMove

Podstawy WinAPI - kolejka komunikatów

WM_SHOW

WM_MOUSEDOWN

WM_MOUSEUP

WM_MOUSEMOVE

WM_MOUSEMOVE

WM_MOUSEDOWN

(System operacyjny)

Wciśnięcie przycisku myszy

Puszczenie przycisku myszy

Przesunięcie myszy

Przesunięcie myszy

Wciśnięcie przycisku myszy

Podstawy WinAPI - responsywność

Aplikacja tworząca okno na pulpicie wchodzi w kontrakt z DWMem –zobowiązuje się do płynnego przetwarzania komunikatów

Jeśli żaden komunikat nie został zdjęty z kolejki w przeciągu 5 sekund, okno jest oznaczone jako zawieszone

Wniosek: musimy dbać o przetwarzanie komunikatów w możliwie jak najkrótszym czasie.

Podstawy WinAPI – WM_PAINT

WM_SHOW

WM_MOUSEDOWN

WM_MOUSEUP

WM_PAINT

WM_MOUSEMOVE

WM_MOUSEDOWN

(System operacyjny)

Wciśnięcie przycisku myszy

Puszczenie przycisku myszy

Invalidate()

Przesunięcie myszy

Wciśnięcie przycisku myszy

Architektura – WinAPI - wnioski

Komunikaty muszą być przetwarzane na bieżąco – najlepiej natychmiast

Wszystkie czasochłonne operacje muszą zostać przesunięte do osobnego wątku

Możemy przechwytywać niskopoziomowe komunikaty w WndProc◦ Ale wszystkie najważniejsze są obsłużone w WinFormsach

Możemy wysyłać komunikaty w celu osiągnięcia konkretnego efektu

Możemy bezkarnie wołać Invalidate() bez wpływu na wydajność◦ Stąd wniosek: czasochłonne (synchroniczne) operacje warto realizować leniwie

Architektura komponentu

Pomysł na architekturę: mysia maszyna stanu

Idle

Pressed Pressed - canceledHoverMouseDown

MouseUp

MouseLeave

MouseEnter

MouseUpMouseEnterprivate enum MouseMode{

Idle,Hover,Pressed,PressedCanceled

}

Implementacja

Implementacja - OnMouseEnter

protected override void OnMouseEnter(EventArgs e){

if (mouseMode == MouseMode.Idle){

mouseMode = MouseMode.Hover;Invalidate();

}

base.OnMouseEnter(e);}

Implementacja - OnMouseLeave

protected override void OnMouseLeave(EventArgs e){

if (mouseMode == MouseMode.Hover){

mouseMode = MouseMode.Idle;Invalidate();

}

base.OnMouseLeave(e);}

Implementacja - OnMouseDown

protected override void OnMouseDown(MouseEventArgs e){

if (mouseMode == MouseMode.Hover){

if (e.Button == MouseButtons.Left){

mouseMode = MouseMode.Pressed;

Invalidate();}

}

base.OnMouseDown(e);}

Implementacja - OnMouseUpprotected override void OnMouseUp(MouseEventArgs e){

if (mouseMode == MouseMode.Pressed){

mouseMode = MouseMode.Hover;

Invalidate();

OnButtonClick();}else if (mouseMode == MouseMode.PressedCanceled){

mouseMode = MouseMode.Idle;

Invalidate();}

base.OnMouseUp(e);}

Implementacja - OnMouseMoveprotected override void OnMouseMove(MouseEventArgs e){

if (mouseMode == MouseMode.Pressed){

if (!this.ClientRectangle.Contains(e.Location)){

mouseMode = MouseMode.PressedCanceled;

Invalidate();}

}else if (mouseMode == MouseMode.PressedCanceled){

if (this.ClientRectangle.Contains(e.Location)){

mouseMode = MouseMode.Pressed;

Invalidate();}

}

base.OnMouseMove(e);}

Implementacja - ctor

public MyButton()

{

this.ResizeRedraw = true;

this.DoubleBuffered = true;

mouseMode = MouseMode.Idle;

}

Implementacja - doublebufferingBez doublebufferingu

Hover

Ramka

Tło

Tekst

Z doublebufferingiem

Hover

Ramka

Tło

Tekst

Hover Render

Live demo

Co zapamiętać?Piszmy kontrolki, gdy są naprawdę potrzebne

Kompozytowa czy niestandardowa?

Maksymalnie wygodny interface (zarówno graficzny jak API)

Kolejka komunikatów WinAPI

Szybka obsługa komunikatów (reponsywność)

Długie obliczenia: synchroniczne – leniwie; asynchroniczne – do osobnego wątku

Komponent - mysia maszyna stanu

Doublebuffering

Co następnym razem?

- Obszar roboczy

- Matematyka: układy współrzędnych

- Metryki

Pytania

?