projektowanie komponentów wizualnych
TRANSCRIPT
Agenda
Czym są kontrolki niestandardowe?
Case study
Rodzaje komponentów
Elementy WinAPI
Architektura i implementacja prostych kontrolek
Podsumowanie
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: 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)
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
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- Start small: przycisk
- Standardowe zachowanie – obserwacja standardowego przycisku
- Wygląd - TODO
Podstawy WinAPI
Window
OK
Handle
Handle
• Size• Style• Icon, small icon• Cursor• Background• MenuName• Text
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
Pomysł na architekturę: mysia maszyna stanu
Idle
Pressed Pressed - canceledHoverMouseDown
MouseUp
MouseLeave
MouseEnter
MouseUpMouseEnterprivate enum MouseMode{
Idle,Hover,Pressed,PressedCanceled
}
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
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