środa, 30 maja 2012

Id autoinkrementowanego pola w bazie

Zdarza się często, iż musimy pobrać id ostatnio wstawionego wiersza w tabeli. Dla różnych serwerów bazodanowych, pobranie tej wartości następuje w inny sposób.
Swoją drogą szkoda że w SQLu nie ma jednej standaryzowanej metody pobierania id wstawionego rekordu. Przez to niestety musimy dla każdej bazy konstruować inny rodzaj inserta.

ORACLE.
Tutaj sprawa jest bardzo prosta i wszystko właściwie załatwia jedno słowo kluczowe - returning:


Code:
INSET INTO Table(Id, FirstName) VALUES(MySeq.nextval, 'Marek') RETURNING Id INTO @id


MS SQL
Tutaj sprawa jest pozornie bardziej skomplikowana. Nie ma takiego wygodnego sposobu jak w Oracle zwrotu wartości pola po wykonanym Insercie.
Funkcją, która zwraca Id jest tutaj SCOPE_IDENTITY(). Tak więc po każdym insercie należy zawołać tę funkcję a ona zwróci numer. Należy pamiętać o jednym bardzo ważnym aspekcie: instrukcja insert i później select muszą być wykonane inline, czyli jedna po drugiej, w przeciwnym razie zamiast oczekiwanego id otrzymamy nulla:


Code:
INSET INTO Table(Id, FirstName) VALUES(MySeq.nextval, 'Marek') RETURNING Id INTO @id

Kolejne bazy będą dodawane wraz z zapotrzebowaniem na zapytania :)
A może macie już gotowe instrukcje pobierania id? Czekam na propozycje w komentarzach.


SQLITE
W tej bazie znam dwa sposoby na znalezienie ostatnio wstawianej wartości. Jeżeli w tabeli istnieje pole autoinkrementowane możemy skorzystać z instrukcji:


Code:
SELECT seq FROM sqlite_sequence WHERE name='TableName'

MS SQL: cycles or multiple cascade paths

Często możemy spotkać się z błędem który brzmi w następujący sposób:

Msg 1785, Level 16, State 0, Line 55
Introducing FOREIGN KEY constraint 'FK_Comments_Users' on table 'Comments' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Msg 1750, Level 16, State 0, Line 55
Could not create constraint. See previous errors.
Co jest powodem tego błędu?
Najłatwiej rozważyć prostą sytuację:



Rozważać właściwie będziemy tylko trzy tabele Users, Articles oraz Comments.
Z diagramu wynika że użytkownik może mieć wiele komentarzy jak i artykułów. Artykuł może mieć wiele komentarzy i jednego autora. Tworząc klucze obce wydawało by się, że prawidłowym rozwiązaniem byłoby stworzenie następujących kluczy:

ALTER TABLE dbo.Articles
  
ADD CONSTRAINT FK_Articles_Users FOREIGN KEY(UserId)
  
REFERENCES dbo.Users(Id)
  
ON DELETE CASCADE
   ON UPDATE
NO ACTION;ALTER TABLE dbo.Comments
  
ADD CONSTRAINT FK_Comments_Articles FOREIGN KEY(ArticleId)
  
REFERENCES dbo.Articles(Id)
  
ON DELETE CASCADE
   ON UPDATE
NO ACTION;ALTER TABLE dbo.Comments
  
ADD CONSTRAINT FK_Comments_Users FOREIGN KEY(UserId)
  
REFERENCES dbo.Users(Id)
  
ON DELETE CASCADE
   ON UPDATE
NO ACTION;


Ostatni klucz jest tutaj kluczowy. Wydaje się logiczne, że jeżeli usuwam użytkownika, powinny zostać usunięte jego komentarze. Zapominamy o jednym. W momencie kiedy usuwamy użytkownika, są usuwane jego artykuły a następnie kaskadowo komentarze do nich. Nie ma potrzeby usuwania ich w sposób kaskadowy jeszcze z poziomu użytkownika.

Tak więc powyższe klucze powinny mieć postać:

ALTER TABLE dbo.Articles
  
ADD CONSTRAINT FK_Articles_Users FOREIGN KEY(UserId)
  
REFERENCES dbo.Users(Id)
  
ON DELETE CASCADE
   ON UPDATE
NO ACTION;
ALTER TABLE dbo.Comments
  
ADD CONSTRAINT FK_Comments_Articles FOREIGN KEY(ArticleId)
  
REFERENCES dbo.Articles(Id)
  
ON DELETE CASCADE
   ON UPDATE
NO ACTION;
ALTER TABLE dbo.Comments
  
ADD CONSTRAINT FK_Comments_Users FOREIGN KEY(UserId)
  
REFERENCES dbo.Users(Id)
  
ON DELETE NO ACTION
  
ON UPDATE NO ACTION;


Pytanie jeszcze może budzić operacja NO ACTION w przypadku instrukcji UPDATE. Nie jest ona tutaj wymagana. Dlaczego? Ponieważ nie przewiduje w aplikacji możliwości zmiany klucza głównego osoby - są to auto numerowane pola i nie będą zmieniane w przyszłości, gdyż wprowadzić mogłoby to więcej chaosu niż pożytku.

Skrypt procedury w bazie MSSQL

Dziś szybki post.
Czasami potrzebujemy pobrać skrypt procedury zawartej już w MSSQL. Jeżeli z jakichś powodów nie da się tego zrobić z poziomu GUI można skorzystać z słowa kluczowego sp_helptex

Przykładowe użycie:
W bazie mamy procedurę GetAllComments, aby pobrać kod za pomocą którego została utworzona, wystarczy użyć następującej składni:

EXECUTE sp_helptext GetAllComments 

Wywołanie tego polecenia SQL da następujący rezultat:



wtorek, 29 maja 2012

Pobranie zawartości zasobów (resources)

Często zasoby są trzymane w plikach zasobów tzw. Resources.
Pliki te są wkompilowywane w aplikację a dodatkowo łatwo dostępne w aplikacji.
Dzisiaj potrzebowałem otrzymać listę zawierającą kolekcję wszystkich elementów zawartych w pliku Resource. Kod który umożliwia taką operację znajduje się poniżej:


Code:
        public static List<string> GetAllResourceKeys()
        {
            var resourceKeys = new List<string>();
            var resourceSet = Resources.ResourceManager.GetResourceSet(CultureInfo.CurrentCulture, true, true);
            foreach (DictionaryEntry key in resourceSet)
            {
                resourceKeys.Add(key.Key.ToString());
            }
            
            return resourceKeys;
        }

sobota, 26 maja 2012

Oracle - stworzenie nowej bazy danych

Bazy Oracle raczej nikomu nie trzeba przedstawić.

W Oracle nie tworzy się stricte bazy danych, a użytkownika który jest powiązany z obiektami występującymi w bazie danych (tabelami, widokami, sekwencjami itd).

Aby utworzyć nowego użytkownika w Oracle postępujemy w następujący sposób:
Za pomocą narzędzia Sql Command Line wprowadzamy kolejno polecenia
connect
system
<password> - hasło które nałożyliśmy podczas instalacji oracl-a
SQL> create user nazwa_uzytkownika identified by password;
SQL> grant connect, resources to nazwa_uzytwkonika;
SQL> exit;

W taki sposób stworzyliśmy użytkownika do którego następnie możemy się połączyć i tworzyć obiekty bazo danowe. 

 

sobota, 12 maja 2012

70-511 Rozdział 3 - Graficzny interfejs użytwkonika - Pędzle

WPF oferuje całkowicie nowe podejście w kwestii tworzenia wizualnego interfejsu użytkownika. Dzięki wykorzystaniu możliwości DirectX można tworzyć dużo bardziej intuicyjny i przyjazny interfejs.

Pędzle
Podstawowy obiekt służący do malowania interfejsu użytkonika. Można go przypisywać do różnych właściwości kontrolki jak np.
  • Background - kolor tła kontrolki
  • BorderBrush - kolor ramki kontrolki
  • Fill - kolor wypełnienia kontrolki
  • Foreground - kolor pierwszego planu, np. wartość tekstowa na przycisku
  • OpacityMask - służy do tworzenia przeźroczystych kolorów
  • Stroke - służy do malowania rogów kontrolki
 Pędzla można użyć aż do momentu gdy nie zostanie wywołana metoda Freeze. Wtedy pędzel staje się tylko do odczytu.

SolidColorBrush
Najprostszy z pędzli - tworzy jednokolorowy pędzel. Dostępne są gotowe predefiniowane pędzle w klasie Brushes:

Code:
Brush aBrush;
aBrush = Brushes.AliceBlue;

Można także podawać kolor heksadecymalne:

Code:
<Button Background="#FFFF0000"></Button>

bądź jako składowe RGB:

Code:
        <Button>
            <Button.Background>
                <SolidColorBrush>
                    <SolidColorBrush.Color>
                        <Color A="255" R="0" G="0" B="255"/>
                    </SolidColorBrush.Color>
                </SolidColorBrush>
            </Button.Background>
        </Button>




Gradient
Pozwala na tworzenie pędzla złożonego z dwóch lub więcej kolorów. Koordynaty przebiegają tak jak jest to przedstawione na poniższym obrazku:

są one relatywne do wielkości obrazka.

Code:
        <Grid>
            <Grid.Background>
                <LinearGradientBrush>
                    <GradientStop Color="Black" Offset="0"/>
                    <GradientStop Color="White" Offset="1"/>
                </LinearGradientBrush>
            </Grid.Background>
        </Grid>

Każdy pędzel tego typu udostępnia kolekcję GradientStop która udostępnia dwie ważne właściwości:
  • Color - kolor
  • Offset - określa miejsce w którym dany kolor nie jest mieszany z innym. 
W przykładzie powyższym górny róg będzie koloru białego, a dolny czarnego, pozostałe miejsce będzie wypełnione mieszaniną tych kolorów
Domyślnie tworzony jest gradient przebiegający w poprzek - od lewego górnego rogu do prawego dolnego. Możemy to zachowanie zmienić ustawiając dwie właściwości: StartPoint oraz EndPoint

Code:
        <Grid>
            <Grid.Background>
                <LinearGradientBrush StartPoint="0,1" EndPoint="1,1">
                    <GradientStop Color="Black" Offset="0"/>
                    <GradientStop Color="White" Offset="1"/>
                </LinearGradientBrush>
            </Grid.Background>
        </Grid>

Jeżeli EndPoint lub StartPoint nie pokrywa całej powierzchni, właściwość Spread decyduje jak będzie pokryta reszta malowanego obiektu. Spred może przyjąć jedną z 3 możliwych wartości:
  • Pad - używa jednorodnego koloru na końcach przedziału
  • Reflect - daje efekt odbitego gradientu na pozostałej powierzchni
  • Repeat - powtarza gradient


RadialGradientBrush
Tworzy gradient w kształcie kół:


Właściwości:
  • GradientOrigin - określa w którym miejscu jest środek gradientu
  • RadiusX - poziomy promień najbardziej zewnętrznego okręgu
  • RadiusY - pionowy promień najbardziej zewnętrznego okręgu

Code:
        <Grid>
            <Grid.Background>
                <RadialGradientBrush Center=".5, .5" RadiusX="0.5" RadiusY=".25">
                    <GradientStop Color="Black" Offset="0"/>
                    <GradientStop Color="BlueViolet" Offset="0.5"/>
                    <GradientStop Color="DarkCyan" Offset="0.5"/>
                    <GradientStop Color="White" Offset="1"/>    
                </RadialGradientBrush>
            </Grid.Background>
        </Grid>



ImageBrush
Pozwala malować po obiektach grafiką pobraną z pliku graficznego:

Code:
        <Grid.Background>
            <ImageBrush ImageSource="Penguins.jpg" Viewbox="0,0,1,1" />
        </Grid.Background>

Za pomocą właściwości Viewbox określamy która część obrazka ma zostać pobrana (koordynaty tak samo jak w przypadku GradientBrush)

Jeżeli właściwość Stretch jest ustawiona na None można zmienić zachowanie pędzla przez ustawienie właściwości TileMode najlepiej przedstawia poniższy obrazek:




VisualBrush
VisualBrush jest bardzo podobny w działaniu do ImageBrush z tym że do malowania używa obiektów które dziedziczą po klasie Visual np. przycisku:

Code:
<VisualBrush Visual="{Binding ElementName=Button1}">
</VisualBrush>

środa, 9 maja 2012

70-511 Rozdział 2 - Zdarzenia i komendy - animacja

Animacje w WPF pozwalają na zmianę wartości właściwości obiektów w określonym czasie. Można zmieniać kolor, rozmiar, położenie albo jakąkolwiek inną właściwość związaną z daną kontrolką.

Animację można podzielić na 3 grupy
  • Liniowe (linear) - pozwalają na zmianę w liniowy sposób danej właściwości. Nazwy rozpoczynają się od <TypeName>Animation np. DoubleAnimation, przedrostek TypeName jest nazwą typu animacji. 
  • Klatkowe (frame) - animacja jest zdefiniowana jako osobne klatki pojawiające się w zdefiniowanym czasie i mające zdefiniowaną wartość. Nazewnictwo: <TypeName>AnimationUsingKeyFrames np. StringAnimationUsingKeyFrames
  • "Ścieżkowe" - (Path based) - tłumaczenie polskie niezbyt dobre jednak chodzi o definiowanie animacji za pomocą obiektu typu Path. Najczęściej używane w przypadku zmiany położenia obiektu na formatce. Nazewnictwo: <TypeName>AnimationUsingPath np. PointAnimationUsingPath

Podstawowe właściwości, które można znaleźć w klasach odpowiedzialnych za animacje:
  • AccelerationRatio - określa jak w czasie zostanie przyśpieszona zmiana wartości animowanej właściwości
  • AutoReverse - określa czy animacja ma być odtwarzana od tyłu po jej zakończeniu
  • BeginTime - określa relatywny czas rozpoczęcia animacji. Dla przykładu: ustawienie tej właściwości na wartość 0:0:5 spowoduje że jej odtwarzanie nastąpi po 5 sekundach od jej uaktywnienia
  • DecelerationRatio - określa w jakim tempie animacja będzie zwalniała podczas wracania do punktu startowego
  • Duration - czas trwania animacji
  • FillBehavior - określa co ma się stać po zakończeniu animacji
  • RepeatBehavior - określa sposób powtarzania animacji
  • SpeedRatio - określa stosunek w jakim dana animacja wykonuje postęp w stosunku do jej rodzica
  • From - określa wartość początkową - jeżeli pominiemy będzie użyta aktualna
  • To - wartość końcowa
  • By - określa czas przez który dana właściwość będzie zmieniana - jeżeli jest ustawiona właściwość To, By jest pomijane


Storyboard
Steruje i kontroluje animacją. W właściwości Children przechowuje obiekty typu Timeline, które zawierają w sobie obiekty Animacji.
Deklaracja:


Code:
        <Storyboard TargetName="Button" TargetProperty="Height">
            <DoubleAnimation Duration="0:0:10" From="1" To="200" />
        </Storyboard>

Jako że TargetName oraz TargetProperty są właściwościami typu attached można także przypiąć je do odpowiedniego elementu:


Code:
        <Storyboard>
            <DoubleAnimation Duration="0:0:10" From="1" To="200" Storyboard.TargetName="Button" Storyboard.TargetProperty="Height" />
        </Storyboard>

Jest to preferowane rozwiązanie, gdyż w większości przypadków StoryBoard będzie zawierał więcej niż jedną animację na raz.


Użycie animacji wraz z triggerami
Aby animacja mogła być uruchomiona można użyć np. triggerów. Ich użycie może być na jeden z dwóch sposobów:
  • style
  • kolekcja triggers kontrolki
Przykład użycia jako dodanie do kolekcji triggerów kontrolki:


Code:
        <Button VerticalAlignment="Top" HorizontalAlignment="Left" Content="Przycisk" 
                Name="Button">
            <Button.Triggers>
                <EventTrigger RoutedEvent="Button.Click">
                    <BeginStoryboard>
                        <Storyboard>
                            <DoubleAnimation Duration="0:0:10" From="1" To="200" Storyboard.TargetName="Button" Storyboard.TargetProperty="Height" />
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger>
            </Button.Triggers>
        </Button>

Klasy akcji które możemy użyć w celu kontroli animacji:
  • BeginStoryboard - służy do rozpoczynania animacji
  • PauseStoryboard - zatrzymuje odtwarzanie animacji
  • ResumeStoryboard - wznawia animację
  • SeekStoryboard - szybkie przewinięcie do żądanego czasu
  • SetStoryboardSpeedRatio - ustawia szybkość odtwarzania animacji
  • SkipStoryboardToFill - przewija do końca animacji
  • StopStoryboard - zatrzymuje odtwarzanie
Przykład poniższy pokazuje w jaki sposób zatrzymać animację:


Code:
        <Style.Triggers>
            <EventTrigger RoutedEvent="Button.MouseEnter">
                <EventTrigger.Actions>
                    <BeginStoryboard Name="stb1">
                        <Storyboard>
                            <DoubleAnimation Duration="0:0:5" Storyboard.TargetProperty="Height" To="200" />
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger.Actions>
            </EventTrigger>
            <EventTrigger RoutedEvent="Button.MouseLeave">
                <EventTrigger.Actions>
                    <StopStoryboard BeginStoryboardName="stb1" />
                </EventTrigger.Actions>
            </EventTrigger>
        </Style.Triggers>

Ważne, aby wszystkie akcje dla jednego StoryBoard były zawarte w jednej kolekcji StoryBoard. Jeżeli będą w różnych, funkcje nie będą wywoływane poprawnie.

Aby działanie SeekStoryboard mogło mieć miejsce, należy ustawić dla niej dwie dodatkowe właściwości:
  • Orgin - może mieć wartość BeginTime lub Duration.
  • Offset - o ile zostanie przewinięta animacja od momentu Orgin


Code:
        <Style.Triggers>
            <EventTrigger RoutedEvent="Button.MouseEnter">
                <EventTrigger.Actions>
                    <BeginStoryboard Name="stb1">
                        <Storyboard>
                            <DoubleAnimation Duration="0:0:10" Storyboard.TargetProperty="Height" To="200" />
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger.Actions>
            </EventTrigger>
            <EventTrigger RoutedEvent="Button.MouseLeave">
                <EventTrigger.Actions>
                    <SeekStoryboard BeginStoryboardName="stb1" Origin="BeginTime" Offset="0:0:5" />
                </EventTrigger.Actions>
            </EventTrigger>
        </Style.Triggers>

Powyższy przykład powoduje przewinięcie animacji 5 sekund od aktualnej pozycji.



Użycie property triggerów
Animację można także używać w przypadku property triggerów. Dostępne są w tym przypadku dwie kolekcje:
  • EnterActions - akcje wykonywane w momencie zadziałania triggera
  • ExitActions - uruchamiane w momencie gdy trigger staje się nieaktywny 
Przykład:


Code:
        <Trigger Property="IsMouseOver" Value="True">
            <Trigger.EnterActions>
                <BeginStoryboard Name="stb1">
                    <Storyboard>
                        <DoubleAnimation Storyboard.TargetProperty="FontSize" To="20" Duration="0:0:.5" />
                    </Storyboard>
                </BeginStoryboard>
            </Trigger.EnterActions>
            <Trigger.ExitActions>
                <StopStoryboard BeginStoryboardName="stb1" />
            </Trigger.ExitActions>
        </Trigger>



Klatkowe animacje
Jak wcześniej wspominałem ten rodzaj animacji definiuje serię klatek. Każda z nich posiada osobno definicję czasu trwania. Do dyspozycji mamy trzy rodzaje klatkowych animacji:
LinearDoubleKeyFrame - przejście między wartościami następuje w sposób jednostajny


Code:
        <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Height">
            <LinearDoubleKeyFrame Value="10" KeyTime="0:0:1" />
            <LinearDoubleKeyFrame Value="100" KeyTime="0:0:2" />
            <LinearDoubleKeyFrame Value="30" KeyTime="0:0:4"/>
        </DoubleAnimationUsingKeyFrames>

DiscreteStringKeyFrame - dla niektórych typów danych np. ciągów znakowych nie można zwiększyć wartości, dlatego stosuje się "dyskretne" zmiany, np:


Code:
        <StringAnimationUsingKeyFrames Storyboard.TargetProperty="Content">
            <DiscreteStringKeyFrame Value="Soup" KeyTime="0:0:0" />
            <DiscreteStringKeyFrame Value="Sous" KeyTime="0:0:1" />
            <DiscreteStringKeyFrame Value="Sots" KeyTime="0:0:2" />
            <DiscreteStringKeyFrame Value="Nots" KeyTime="0:0:3" />
            <DiscreteStringKeyFrame Value="Nuts" KeyTime="0:0:4" />
        </StringAnimationUsingKeyFrames>

Spline key frames - zmiany są wyrażone przy użyciu krzywej Beziera:


Code:
<SplineDoubleKeyFrame Value="300" KeyTime="0:0:6" KeySpline="0.1,0.8 0.6,0.6" />

wtorek, 8 maja 2012

70-511 Rozdział 2 - Zdarzenia i komendy - komendy

Komendy to nowy sposób obsługi często powtarzanych czynności. Przykładami komend w WPF są np. Copy, Paste, Cut.
Wbudowane komendy znajdziemy w jednej z 5 statycznych klas:
  • ApplicationCommands
  • ComponentCommands
  • EditingCommands
  • MediaCommands
  • NavigationCommands
Procedura implementacji komendy:
  1. Decyzja czy będzie używana jedna z dostępnych (zaimplementowanych już) komend czy będzie to stworzona nowa implementacja
  2. Skojarzenie komendy z kontrolkami
  3. Stworzenie metody obsługującej komendę
  4. Stworzenie kontrolki CommandBinding bindującej komendę z uchwytem
  5. Dodanie bindingu komendy do kolekcji Commands okna, tam gdzie wywoływana jest komenda

Skojarzenie komendy z kontrolką
Wiele kontrolek implementuje interfejs ICommandSource dzięki czemu udostępniają właściwość Command, np. Button. Komenda jest wywoływana w tym przypadku po wciśnięciu przycisku:


Code:
<Button Command="ApplicationCommands.Find">Button</Button>


Wywołanie komendy za pomocą gestów
Komendę można także skojarzyć z gestami myszy czy zdarzeniami klawiatury:


Code:
ApplicationCommands.Find.InputGestures.Add(new
MouseGesture(MouseAction.LeftClick, ModifierKeys.Control));
ApplicationCommands.Find.InputGestures.Add(new
KeyGesture(Key.Q, ModifierKeys.Control));



Wywołanie komendy z kodu:
Jeżeli kontrolka nie udostępnia właściwości Command, można zawartość procedury komendy wywołać bezpośrednio z kodu:


Code:
ApplicationCommands.Find.Execute(aParameter, TargetControl);

aParameter - parametr komendy (jeżeli nie jest wymagany można przesłać null)
TargetControl - tutaj zostanie rozpoczęte poszukiwanie metodą bubble w poszukiwaniu CommandBinding.


Uchwyty komend
Sygnatura uchwytu do komendy:


Code:
private void myCommandHandler(object sender, ExecutedRoutedEventArgs e)
{
// obsługa
}




Command Bindings
Obiekt tego typu skleja całą architekturę komend - łączy komendę z jej uchwytem. Dodając obiekt typu CommandBinding do kolekcji CommandBindings okna lub innej kontrolki umożliwia wywołanie kodu w momencie zadziałania komendy. Poniżej kod umożliwiający stworzenie i rejestrację CommandBinding:


Code:
CommandBinding abinding = new CommandBinding();
abinding.Command = ApplicationCommands.Find;
abinding.Executed += new ExecutedRoutedEventHandler(myCommandHandler);
this.CommandBindings.Add(abinding);


Command Bubbling
Każda kontrolka posiada kolekcję CommandBindings. Jest to związane z tym iż podobnie jak w Routed Events tu także występuje mechanizm bubblingu. Przerwanie tego mechanizmu przebiega tak samo jak dla zdarzeń.


Wyłączanie komend
Jeżeli komenda nie uczestniczy w bindingu automatycznie jest wyłączona. Innym sposobem jest użycie metody CanExecute która zwraca wartość typu bool mówiącą czy dana komenda jest możliwa do wykonania czy też nie.
Aby przechwycić zdarzenie CanExecute:
1. Tworzymy metodę która determinuje czy można wykonać komendę:


Code:
bool canExecute;
void abinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = canExecute;
}

2. Dla bindingu dodajemy obsługę zdarzenia CanExecute:


Code:
abinding.CanExecute += new CanExecuteRoutedEventHandler(abinding_CanExecute);

Można także to zrobić w kodzie XAML:


Code:
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Find"
Executed="CommandBinding_Executed"
CanExecute="abinding_CanExecute" />
</Window.CommandBindings>



Tworzenie własnych komend
W tym przypadku Microsoft zaleca stworzenie statycznej klasy która eksponuje komendę do użycia:


Code:
    public static class MyCommands
    {
        private static RoutedUICommand launch_command;

        static MyCommands()
        {
            InputGestureCollection myInputGestures = new
                InputGestureCollection();
            myInputGestures.Add(new KeyGesture(Key.L, ModifierKeys.Control));
            launch_command = new RoutedUICommand("Launch", "Launch",
                                                 typeof (MyCommands), myInputGestures);
        }

        public RoutedUICommand Launch
        {
            get { return launch_command; }
        }
    }

70-511 Rozdział 2 - Zdarzenia i komendy - zdarzenia

WPF oferuje znacznie bardziej rozbudowany system obsługi zdarzeń niż ten znany z WindowsForms. WPF udostępnia tzw. routet events, mechanizm umożliwiający przechwytywanie zdarzenia na wielu poziomach hierarchii drzewa wizualnego.

3 typy routed events:
  • Direct Events - zdarzenia, które są najbliższe tym znanym z WindowsForms. Zdarzenie to jest wywoływane i obsługiwane tylko u źródła, przykład: MouseLeave'
  • Bubbling events - wywołane najpierw u źródła, a następnie przechodzi w dół drzewa wizualnego. Przykładem może być okno w którym na panelu leży label. Zdarzenie MouseDown wywołane na labelu, przejdzie na panel a następnie na okno. Na każdym z tych poziomów możemy je obsłużyć.
  • Tuneling Events - jest to zdarzenie działające odwrotnie do Bubbling event, swoją drogę rozpoczyna w najstarszym przodku, aż do kontrolki która faktycznie je wywołała
Zdarzenia typu Tunelling są w WPF oznaczane słówkiem Preview np PreviewKeyDown.
Zdatzenie typu Tunelling są zawsze wykonywane przed zdarzeniami typu Bubbling.


Routet events args
Klasa ta zawiera bogate informacje o zdarzeniu oraz jego źródła. Właściwości które można w niej znaleźć:
  • Handled - determinuje czy zdarzenie zostało obsłużone. Można dzięki temu zatrzymać dalszy postęp bubblingu lub tunelingu
  • OriginalSource - zwraca informację o źródle skąd pochodzi zdarzenie. Przeważnie zwraca to samo co właściwość Source, dla kontrolek typu composite może być to jednak inna wartość
  • RoutedEvent - w przypadku łapania więcej niż jednego zdarzenia za pomocą tego samego uchwytu, można dzięki tej właściwości zidentyfikować zdarzenie
  • Source - zwraca obiekt który wywołał zdarzenie


Dodawanie uchwytu
Uchwyt do zdarzenia najlepiej dodać w XAML-u:


Code:
<Button VerticalAlignment="Top" Click="button1_Click">Button</Button>

Istnieje możliwość przypięcia zdarzenia do kontrolki, która w normalnych warunkach go nie rzuca. Są to tzw. Attached Events. Dla przykładu można przypiąć zdarzenie kliknięcia Gridowi, który posiada button:


Code:
    <Grid Button.Click="Grid_Click">
        <Button Name="button1" VerticalAlignment="Top" >Button</Button>
    </Grid>



EventManager
Jest to klasa, która zajmuje się rejestracją wszystkich zdarzeń w aplikacji WPF. Właściwości:
  • GetRoutedEvents - zwraca tablicę wszystkich zaarejestrowanych Routed Events
  • GetRoutedEventsForOwner - zwraca tablicę wszystkich zarejestrowanych zdarzeń dla podanego obiektu
  • RegisterClassHandler - rejestruje uchwyt na poziomie klasy
  • RegisterRoutedEvent - rejestruje uchwyt na poziomie instancji klasy


Tworzenie nowego Routed Event
1. Tworzymy publiczne, tylko do odczytu zdarzenie:


Code:
public static readonly RoutedEvent MyEvent;

2. Tworzymy wrapper dla Routed Event:


Code:
        public event RoutedEventHandler MyEventWrapper
        {
            add
            {
                AddHandler(MyEvent, value);
            }
            remove
            {
                RemoveHandler(MyEvent, value);
            }
        }

3. Używając EventManager rejestrujemy zdarzenie w konstruktorze klasy, która będzie go używać:


Code:
EventManager.RegisterRoutedEvent("MyEvent", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(Button));

Podajemy kolejno: nazwę zdarzenia, rodzaj, typ delegaty, klasę która jest jego właścicielem.
Wywoływanie zdarzenia w kodzie polega na stworzeniu obiektu klasy RoutedEventArgs, a następnie wywołanie zdarzenia za pomocą metody RaiseEvent:


Code:
            RoutedEventArgs myEventArgs = new RoutedEventArgs(myControl.myNewEvent);
            RaiseEvent(myEventArgs);



Tworzenie uchwytu na poziomie klasy
Uchwyt taki wywoływany jest dla każdej klasy żądanego typu i jest wywoływany przed uchwytem na poziomie instancji klasy. Tworzenie takiego uchwytu:

1. Tworzymy statyczną metodę o takiej samej sygnaturze jak zdarzenie:


Code:
private static void SuperClickHandlerMethod(object sender, RoutedEventArgs e)
{
//obsługa
}

2. W statycznym konstruktorze, który będzie korzystał z tego zdarzenia tworzymy delegate do metody:


Code:
RoutedEventHandler MyHandler = new RoutedEventHandler(SuperClickHandlerMethod);

3. Także w statycznym konstruktorze rejestrujemy uchwyt:


Code:
EventManager.RegisterClassHandler(typeof(Button),
SuperClickEvent,MyHandler);



Zdarzenia na poziomie aplikacji
Klasa Application zawiera standardowe zdarzenia, które mogą zajść podczas korzystania z aplikacji. Wybrane z nich:
  • Activated - wywoływane podczas pierwszego uruchomienia aplikacji jak i przy przejściu z innej do naszej
  • Deactivated - wywołuje się w momencie przejścia do innej aplikacji
  • DispatcherUnhandledException - Wywoływany w momencie gdy wyjątek nie zostanie nigdzie obsłużony
  • Exit - wywołuje się niezależnie od powodu zamknięcia aplikacji
  • SessionEnding - wywoływany w momencie gdy użytkownik wylogowuje się lub zamyka komputer
  • Startup - wywoływany w momencie uruchamiania aplikacji
 Instrukcja tworzenia uchwytu na poziomie aplikacji:
 1. W pliku App.xaml dodajmy zdarzenie:


Code:
<Application x:Class="Application"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="Window1.xaml" Startup="App_Startup">

2. W kodzie C# obsługujemy je:


Code:
void App_Startup(object sender, StartupEventArgs e)
{
// obsługa
}

poniedziałek, 7 maja 2012

70-511 Rozdział 1 - Tworzenie interfejsu użytwkownika - Style i triggery

W tej części zostaną omówione style oraz triggery.

Style
Style w WPF są podobne do stylów w HTML - CSS. Definiując style, łatwiej wpływać na wygląd całej aplikacji. Klasa Style posiada następujące właściwości:
  • BasedOn - pozwala na "dziedziczenie" z innego stylu
  • Resources - kolekcja lokalnych zasobów dla stylu
  • Setters - kolekcja Setterów dla właściwości bądź zdarzeń
  • TargetType - typ elementu który ma przyjąć styl
  • Triggers - kolekcja triggerów, pozwalająca na zaprojektowanie zmian w interfejsie użytkownika w przypadku zmiany wartości którejś z właściwości

Settery
Można je podzielić na dwie kategorie:
  • settery dla właściwości - ustawiają wartości właściwości
  • settery dla zdarzeń - ustawiają uchwyty dla zdarzeń
Dwie ważne właściwości:
  • Property - określa która właściwość ma być ustawiona
  • Value - wartość, którą ma przyjąć właściwość
Setter ma następującą składnię:


Code:
<Setter Property="Element.PropertyName" Value="Red" />

Przykład:


Code:
<Style>
<Setter Property="Control.Background" Value="Red" />
</Style>



EventSetter
Podobnie jak setter dla właściwości, posiada dwie ważne właściwości:
  • Event - typ zdarzenia któemu chcemy nadać wartość
  • Handler - uchwyt
Właściwość Event ma następującą składnię: Element.EventName


Code:
<EventSetter Event="Button.MouseEnter" Handler="Button_MouseEnter" />


Style można przypisywać bezpośrednio do kontrolki:


Code:
<Button Height="25" Name="Button1" Width="100">
<Button.Style>
<Style>
<Setter Property="Button.Content" Value="Style set directly" />
<Setter Property="Button.Background" Value="Red" />
</Style>
</Button.Style>
</Button>

bądź pisać je jako zasoby:


Code:
<Window.Resources>
<Style x:Key="StyleOne">
<Setter Property="Button.Content" Value="Style defined in resources" />
<Setter Property="Button.Background" Value="Red" />
</Style>
</Window.Resources>

Przypisanie stylu do przycisku:


Code:
<Button Name="Button1" Style="{StaticResource StyleOne}" Height="30" Width="200" />

Styl można przypisać do wszystkich kontrolek danego typu ustawiając właściwość TargetType:


Code:
<Window.Resources>
<Style TargetType="Button">
<Setter Property=" Content" Value="Style set for all buttons" />
<Setter Property="Background" Value="Red" />
</Style>
</Window.Resources>

Taki zabieg dodatkowo spowoduje że nie trzeba używać pełnego kwalifikatora dla właściwości której przypisujemy wartość


Ustawianie stylów z poziomu kodu:
Style dużo łatwiej definiuje się w XAML, jednak czasami zachodzi konieczność dynamicznego ich tworzenia np. w wypadku aplikacji w której użytkownik chce sam ustalić wygląd aplikacji. Przykładowy kod:


Code:
Style aStyle = new Style();
Setter aSetter = new Setter();
aSetter.Property = Button.BackgroundProperty;
aSetter.Value = Brushes.Red;
aStyle.Setters.Add(aSetter);
Setter bSetter = new Setter();
bSetter.Property = Button.ContentProperty;
bSetter.Value = "Style set programmatically";
aStyle.Setters.Add(bSetter);
Button1.Style = aStyle;

Można także zdefiniować styl w XAML i dynamicznie go przypisać do kontrolki:


Code:
<Window.Resources>
<Style x:Key="StyleOne">
<Setter Property="Button.Content" Value="Style applied in code" />
<Setter Property="Button.Background" Value="Red" />
</Style>
</Window.Resources>


Style aStyle;
aStyle = (Style)this.Resources["StyleOne"];
Button1.Style = aStyle;


Dziedziczenie stylów:
Jeżeli mamy zdefiniowany jeden styl dla przycisków, a chcemy aby na formatce jeden z nich się odróżniał, możemy dziedziczyć styl i np. zmienić tylko dla niego kolor np.




Triggery
Triggery pozwalają na zmianę wartości kontrolek w reakcji na zmianę innej wartości. Wyróżniamy 5 rodzai triggerów:
  • Property trigger (Trigger) - w momencie gdy wartość właściwości przyjmuje wartość Value zostaje aktywowany
  • Multi-trigger (MultiTrigger) - sprawdza na raz kilka wartości; zostaje aktywowany w momencie kiedy wszystkie są zgodne z wartością Value
  • Data trigger (DataTrigger) - sprawdza zbindowane pola, aktywuje się gdy wartość pola ma wartość równą polu Value
  • Multi-data trigger (MultDataTrigger) - to samo co wyżej tylko że dla kilku pól
  • Event trigger (EventTrigger) - wykonuje akcje w momencie gdy dane zdarzenie ma miejsce
Trigery inne niż EventTrigger mogą być definiowane tylko w kodzie stylu, w innym przypadku dostaniemy błąd w czasie wykonania.


Property Triggers
Najczęściej wykorzystywany rodzaj triggerów. Ważne właściwości tego rodzaju triggerów:
  • EnterActions - kolekcja obiektów typu Akcja. Akcje są wykonane w momencie, gdy trigger zostaje aktywowany
  • ExitActions - kolekcja akcji, które są wykonywane w momencie gdy trigger przestaje być aktywny
  • Property - właściwość, która ma być monitorowana
  • Setters - kolekcja setterów, które są aplikowane w momencie gdy trigger się aktywuje
  • Value - wartość do porównania z właściwością Property
Prosty przykład zmiany czcionki, gdy myszką najedziemy na przycisk:


Code:
<Style.Triggers>
<Trigger Property="Button.IsMouseOver" Value="True">
<Setter Property="Button.FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>


Multi-triggers
Sprawdzanych jest kilka właściwości jednocześnie. W momencie gdy wszystkie warunki zajdą, nadawana jest odpowiednia wartość wyspecyfikowanym właściwością. Przykład:


Code:
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Button.IsMouseOver" Value="True" />
<Condition Property="Button.IsFocused" Value="True" />
</MultiTrigger.Conditions>
<MultiTrigger.Setters>
<Setter Property="Button.FontWeight" Value="Bold" />
</MultiTrigger.Setters>
</MultiTrigger>
</Style.Triggers>

W tym przypadku kolor czcionki przycisku zostanie pogrubiony kiedy myszka znajduje się nad przyciskiem i jest ustawiony na nim focus.


Data Triggers i Multi-data Triggers
Są bardzo podobne do Triggerów, z tym że sprawdzają zmiany w polach które są bindowane. Przykład:


Code:
<Style.Triggers>
<DataTrigger Binding="{Binding Path=CustomerName}" Value="Fabrikam">
<Setter Property="Label.Background" Value="Red" />
</DataTrigger>
</Style.Triggers>

Multi-data triggers są podobne do Multi-triggers, tak samo posiadają kolekcję warunków oraz setterów, jednak nasłuchują pól zbindowanych:


Code:
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=CustomerName}" Value="Fabrikam" />
<Condition Binding="{Binding Path=OrderSize}" Value="500" />
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Label.Background" Value="Red" />
</MultiDataTrigger.Setters>
</MultiDataTrigger>
</Style.Triggers>



Event Triggers
Są wywoływanie w momencie, gdy wyspecyfikowane zdarzenie ma miejsce. Wykorzystywane są przede wszystkim w przypadku animacji. Dwa proste przykłady:

Code:
<EventTrigger RoutedEvent="Button.Click">
<SoundPlayerAction Source="C:\myFile.wav" />
</EventTrigger>

W momencie naciśnięcia przycisku zostanie odtworzony utwór. Drugi przykład:


Code:
<EventTrigger RoutedEvent="Button.Click">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Duration="0:0:5"
Storyboard.TargetProperty="Height" To="200" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>

W momencie gdy przycisk zostanie kliknięty, jego wysokość zostanie zwiększona do 200 jednostek.


Kolejność w jakiej jest ustalana wartość właściwości. Najniższy numer oznacza najwyższy priorytet:
  1. Ustawienie przez system coerci.
  2. Ustawione przez animacje
  3. W kodzie C#, XAML, bądź bindowanie
  4. Szablon
  5. Niejawny styl
  6. Triggery Stylu
  7. Triggery Szablonu
  8. Domyślny styl
  9. Dziedziczenie
  10. Metadata

sobota, 5 maja 2012

70-511 Rozdział 1 - Tworzenie interfejsu użytwkownika - Zasoby

W tym poście zostaną omówione zasoby, czyli obiekty i pliki które nie są tworzone w czasie działania programu, a dostarczone przed kompilacją.


Używanie binarnych zasobów (plików)
Aby dodać zasób do naszej aplikacji, postępujemy według poniższego schematu:
  1. Z menu projektu wybieramy Add Existing Item
  2. Po otwarciu okna dialogowego wyboru plików, wskazujemy plik który chcemy dodać
  3. W oknie właściwości zmieniamy dla naszego pliku właściwość Build Action na Resource.
Ważne! Dla WPF nie ustawiamy nigdy Build Action na Embedded Resource
Uaktualnienie pliku zasobów odbywa się poprzez ponowne wykonanie powyższych kroków.


Ładowanie zasobów
Ładowanie zasobów np. do kontrolki Image jest bardzo proste. Wystarczy do właściwości Source podstawić ścieżkę do pliku graficznego. W zależności czy plik znajduje się na tej samej wysokości w drzewie folderów, czy na innym ścieżka przyjmie odpowiednią postać:


Code:
        <Image Source="folder/pic.png" Stretch="Fill"/>
        <Image Source="pic.png" Stretch="Fill"/>


Pack URIs
Pokazana powyżej składnia jest skróconą wersją tzw. Pack URI. Pełna składnia ma następującą postać:
pack://<Authority>/<Folder>/<FileName>
  • Authority przyjmuje jedną z dwóch postaci application:,,, - jeżeli zasób pochodzi z aktualnie uruchomionej aplikacji lub siteOfOrigin:,,, - aplikacja ma szukać zasobu w jej źródłach.
  • Folder - folder w którym znajduje się zasób
  • FileName - nazwa zasobu 
Przydaje się to zwłaszcza w przypadku podawania ścieżki do zasobu w kodzie:


Code:
image.Source = new BitmapImage(new Uri("pack://application:,,,/folder/pic.png"));


Używanie zasobów z innych bibliotek Dll
Jest możliwe użycie zasobu znajdującego się w innej bibliotece dll niż aplikacja. W tym celu należy skorzystać z następującego Pack URI:
pack://application:,,,/<AssemblyName>;component/<Folder>/<FileName>


Pliki nie wkompilowywane
W dużej ilości przypadków będzie potrzeba wymiany któregoś z plików, np. będzie to szablon na podstawie którego będą generowane zaproszenia. Użycie w takim przypadku wkompilowywanych zasobów, zmuszałoby nas do kompilacji aplikacji za każdym razem kiedy szablon się zmienił. Dodatkowo pliki dźwiękowe czy też filmowe, które są odtwarzane za pomocą kontrolki MediaPlayer nie mogą korzystać z składni Pack URI.
Aby dodać plik jako zasób nie kompilowany, postępujemy według poniższych kroków:
  1. Z menu projektu wybieramy opcję Add Existing Item.
  2. Wybieramy plik który chcemy dodać
  3. Ustawiamy we właściwościach Build Action na Content oraz Copy To Output Directory na Copy Always
Po dodaniu pliku używamy relatywnej ścieżki przy odwołaniu się do niego:


Code:
<MediaElement Source="music.mp3"/>



Użycie siteOfOrigin Pack URI dla plików często zmienianych
Zdarza się, że mamy zamiar użyć pliku który jeszcze nie znajduje się w solucji bądź też będzie często zmieniany. W przypadku XAML-a plik musi się znajdować już w czasie kompilacji w odpowiednim katalogu aby można go było użyć w aplikacji. Rozwiązaniem tego problemu jest wcześniej wspomniany siteOfOrigi, który nie wkompilowuje zasobu do Dll. Dzięki temu plik może być podmieniony bez potrzeby rekompilacji całej aplikacji. Przykład:


Code:
<Image Source="pack://siteOfOrigin:,,,/image.jpg"/>




Odwołanie do zasobów z kodu
Do zasobów zawartych w aplikacji można odwołać się z kodu C#:


Code:
            StreamResourceInfo myInfo = Application.GetResourceStream(new Uri("text.txt", UriKind.Relative));
            var myReader = new StreamReader(myInfo.Stream);
            var textContent = myReader.ReadToEnd();

obiekt klasy StreamResourceInfo posiada dwie właściwości:
  • Stream - strumień do zawartości pliku
  • ContentType - rodzaj zawartości


Tworzenie Dll zawierających tylko zasoby
Rozwiązanie to jest przydatne w przypadku gdy chcemy często zmieniać pliki zasobów. Dzięki stworzeniu takiej Dll, nie ma potrzeby kompilowania i podmiany następnie całej aplikacji, a wystarczy podmienić jedną bibliotekę na nową. Kroki które należy wykonać aby stworzyć taką bibliotekę:
  1. W VS tworzymy projekt typu Empty Project.
  2. W Solution Explorer klikamy prawym przyciskiem myszy na nazwie projektu i wybieramy opcję Properties. Na zakładce Application, ustawiamy Application Type na Class Library
  3. W menu projektu wybieramy Add Existing Item aby dodać plik zasobów.
  4. Dodanemu pliku zmieniamy Build Action na Embedded Resource
  5. Budujemy Dll
Dostęp z kodu do zasobu:
  1. Pobieramy nazwę Dll: 
    Code:
    System.Reflection.AssemblyName.GetAssemblyName("C:\\myAssembly.dll"));
  2. Ładujemy Dll-kę do pamięci używając pobranej nazwy:
    Code:
    var asm = System.Reflection.Assembly.Load(aName);
  3. Następnie za pomocą metod GetManifestResourceNames - pobieramy nazwy zasobów w Dll a za pomocą metody GetManifestResourceStream - pobieramy strumień do nich. Poniżej przykład pobrania obrazka i przypisania go do kontrolki Image:
    Code:
    var res = asm.GetManifestResourceNames();
    pictureBox1.Image = new
    System.DrawingBitmap(asm.GetManifestResourceStream(res[0]));


Używanie logicznych zasobów
Zasoby logiczne to zasoby które nie są widoczne w wizualnym drzewie naszej aplikacji. Przykładem takiego zasobu jest np. Brush. Dzięki temu raz stworzony zasób pędzla możemy użyć w wielu miejscach.
Resource Logiczne możemy tworzyć na różnych poziomach: od aplikacji po zasoby kontrolki, w zależności gdzie chcemy reużywać raz stworzony zasób. Przykład:


Code:
<Window.Resources>
<RadialGradientBrush x:Key="myBrush">
<GradientStop Color="CornflowerBlue" Offset="0" />
<GradientStop Color="Crimson" Offset="1" />
</RadialGradientBrush>
</Window.Resources>

Każdy zasób musi mieć właściwość x:Key która jest unikalna w danej kolekcji zasobów.
Dostęp do tak zdefiniowanych zasobów odbywa się poprzez przypisanie zasobu do właściwości która jest z nim kompatybilna np.:


Code:
<Grid Background="{StaticResource myBrush}">
</Grid>



Statyczne i dynamiczne zasoby
Oprócz statycznych zasobów istnieją także dynamiczne zasoby. Główne różnice to:
  • Dynamiczne zasoby są tworzone za każdym odwołaniem się do nich
  • Ponieważ są tworzone za każdym razem, zmniejszają wydajność ogólną aplikacji
Jak jest więc sens ich stosowania? Jeżeli w kodzie chcielibyśmy zmienić np. kolor stosowany przez pędzel, to w przypadku StaticResource zmiana nie odniesie rezultatu, w przypadku DynamicResources zmiana zostanie odnotowana przez użytkownika.



Słowniki zasobów (Resource Dictionary)
Słowniki zasobów tworzą osobny plik w którym są przechowywane zasoby. Dzięki temu można je reużywać w wielu aplikacjach w prosty sposób. Aby dodać nowy słownik postępujemy według poniższych kroków:
  1. Dodajemy nowy element do solucji typu Resource Dictionary
  2. W pliku dodajemy kolejne zasoby
Aby można było teraz użyć zasobów ze słownika, należy go połączyć z zasobami aplikacji (bądź okna lub innej kontrolki gdzie chcemy z nich skorzystać). Przykład mergowania:


Code:
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Dictionary1.xaml" />
<ResourceDictionary Source="Dictionary2.xaml" />
</ResourceDictionary.MergedDictionaries>
<SolidColorBrush x:Key="BlueBrush" Color="Blue" />
</ResourceDictionary>
</Window.Resources>



Dostęp do zasobów w kodzie
Zasoby w kodzie można zarówno odczytywać jak i nadpisywać. Dostęp jest możliwy na dwa sposoby:
  • Za pomocą metody (SolidColorBrush)Button1.FindResource("myBrush");
  • Odwołując się konkretnie do zasobów kontrolki (SolidColorBrush)this.Resources["myBrush"];