Pokazywanie postów oznaczonych etykietą handler. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą handler. Pokaż wszystkie posty

czwartek, 16 sierpnia 2012

[WPF] Routed Events

Routed events to specjalny rodzaj zdarzeń, które świetnie nadają się do pracy z hierarchiczną strukturą kontrolek. Kiedy Routed Event jest wywoływany, może być propagowany w górę lub dół drzewa, co może się okazać w wielu sytuacjach bardzo użytecznym rozwiązaniem. Dzięki nim logika aplikacji nie musi pamiętać o strukturze drzewa wizualnego, co jest bardzo dobrą właściwością np. przy stylowaniu. Istnieje pewna analogia pomiędzy RE, a Dependency Property. Oba  są definiowane jako publiczne statyczne pole, rejestrowane w podobny sposób i oba można "opakować". Dla DP pobiera się lub ustawia wartość, dla RE dodaje lub zabiera Event Handler. Istnieją trzy strategie routingu :
  • Tunneling - zdarzenie jest wywoływane najpierw w korzeniu drzewa, a następnie na niższych poziomach aż do źródła wywołania lub obsługi zdarzenia
  • Bubbling - zdarzenie wołane u źródła przechodzi na wyższe poziomy do korzenia lub do miejsca, w którym zostaje obsłużone
  • Direct - zachowanie takie, jak u zwykłych .NETowych eventów (wołane tylko w źródle), ale można tworzyć dla tego zdarzenia event triggery.
 Obsługa RE przypomina obsługę zwykłych zdarzeń (metody z pierwszym parametrem typu object (sender), oraz drugim typu eventArgs lub pochodnym).

Klasa UI element oferuje wiele eventów ze zdefiniowaną strategią bubbling jak i tunneling. Przez konwencję, zdarzenia ze strategią tunneling nazywane są z przedrostkiem Preview. Np. PreviewMouseDown to zdarzenie ze strategią tunneling, podczas gdy MouseDown ma zdefiniowaną strategię typu bubbling.

Na poniższym przykładzie można zaobserwować kolejność, w jakiej są wywoływane zdarzenia, oraz warunki, w jakich routing jest przerwany.

<Window x:Class="Routed_Events.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid MouseLeftButtonDown="Grid_MouseLeftButtonDown"           
          PreviewMouseRightButtonDown="Grid_PreviewMouseRightButtonDown">
            <Button Content="Click Me"            
            PreviewMouseLeftButtonDown="Button_PreviewMouseLeftButtonDown" 
            MouseRightButtonDown="Button_MouseRightButtonDown" Margin="75,112,278,140" />

        </Grid>
       
    </Grid>
   
    
</Window>


  public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Grid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            Debug.WriteLine("LeftMouseButton handled by grid1");
        }

        private void Grid_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            Debug.WriteLine("RightMouseButton handled by grid1");
        }

        private void Button_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            Debug.WriteLine("LeftMouseButton handled by button1");
        }

        private void Button_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            Debug.WriteLine("RightMousebutton handled by button1");
        }
     
    }


LeftMouseButton handled by button1
RightMouseButton handled by grid1
RightMousebutton handled by button1

Programista może definiować swoje Routed Events w następujący sposób:

public class MyControl : Button
    {
        public static readonly RoutedEvent EvenClickEvent;
        public static int Counter = 0;

        static MyControl()
        {

            MyControl.EvenClickEvent = EventManager.RegisterRoutedEvent("EvenClick",
                RoutingStrategy.Direct, typeof(RoutedEventHandler), typeof(MyControl));
        }

        public event RoutedEventHandler EvenClick
        {
            add { AddHandler(MyControl.EvenClickEvent, value); }
            remove { RemoveHandler(MyControl.EvenClickEvent, value); }
        }

        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            if(Counter++ % 2 != 0)
                RaiseEvent(new RoutedEventArgs(MyControl.EvenClickEvent, this));
        }
    }

Dzięki temu można obsługiwać taki event z poziomu xaml.

niedziela, 8 lipca 2012

[Wzorce projektowe] Chain of Responsibility

Analogia z życia:

Często zdarza się, że w dużej korporacji szeregowy pracownik zgłasza zapotrzebowanie zakupu jakiegoś drogiego urządzenia, np. tokarki CNC za cenę ponad 100 000 zł. Zgłoszenie to przekazuje swojemu kierownikowi. Kierownik rozważa pewne za i przeciw i może takie zgłoszenie odrzucić, lub przekazać do dyrektora działu. Dyrektor działu konsultuje pomysł ze specjalistami i gdy uzna, że takie urządzenia będzie przydatne, zgłasza zapotrzebowanie do prezesa firmy, a ten kontaktuje się z pracownikiem aby omówić szczegóły zakupu. Jednak w przypadku, gdy pracownik zgłosi zapotrzebowanie na coś tańszego, zgłoszenie zostanie zaakceptowane na niższym poziomie, np. zakup monitora może zatwierdzać kierownik, bez wiedzy przełożonych.



Zastosowanie:

Łańcucha odpowiedzialności używa się wszędzie tam, gdzie mamy do czynienia z wiadomością przekazywaną od nadawcy, do hierarchii odbiorców. Zależnie od treści wiadomości, pierwszy odbiorca może ją przekazać do następnego odbiorcy lub samemu przetworzyć i wysłać odpowiedź, nie informując o tym następnych odbiorców. Nadawca jest zawsze świadomy tylko pierwszego odbiorcy, natomiast każdy odbiorca wie tylko o następnym nadawcy. Nadawca nie wie, kto konkretnie przetworzył wiadomość, interesuje go tylko odpowiedź. Warto także pamiętać o tym, że kolejność jest ważna i należy także obsłużyć przypadek, gdy żaden odbiorca nie przetworzy wiadomości. Zastosowanie wzorca umożliwia dynamiczne zarządzanie MessageHandlerami a także ich hierarchizację.

Zasada działania:

W pierwszej kolejności należy utworzyć interfejs reprezentujący wiadomość, którą należy przetworzyć, a także zastanowić się nad tym, w jakiej postaci ma być przekazywana odpowiedź np. enum. Drugi interfejs będą implementowali odbiorcy wiadomości, którzy będą musieli przeładować metodę zwracają odpowiedź (np. enum) a przyjmującą obiekt typu wiadomości. Odbiorca musi także przechowywać referencję do następnego odbiorcy, który jest wywoływany, gdy odpowiedź nie może zostać przetworzona. Należy pamiętać o zaimplementowaniu w jakiś sposób ostatniego ogniwa z łańcucha.

Przykład implementacyjny:


public interface IPurchaseRequest
    {
        decimal Cost { get; set; }
    }

    public class PurchaseRequest : IPurchaseRequest
    {
        public string Name { get; set; }

        public PurchaseRequest(decimal cost)
        {
            Cost = cost;
        }

        public decimal Cost
        {
            get; set; 
        }
    }

    public interface IPurchaseApprover
    {
        Response ConsiderRequest(IPurchaseRequest request);
        void SetNextSupervisor(IPurchaseApprover approver);
    }

    public enum Response
    {
        Granted,
        Denied,
        NotAllowedToDecide
    }
public class Supervisor : IPurchaseApprover
    {
        public decimal ApprovalLimit { get; set; }
        public IPurchaseApprover Next { get; set; }
        public string Name { get; set; }

        public Supervisor(string n, decimal l)
        {
            ApprovalLimit = l;
            Name = n;
            Next = EndOfChain.Instance;
        }

        public void SetNextSupervisor(IPurchaseApprover approver)
        {
            Next = approver;
        }

        public Response ConsiderRequest(IPurchaseRequest request)
        {
            if (request.Cost < ApprovalLimit)
            {
                return Response.Granted;
            }
            return Next.ConsiderRequest(request);
        }
    }
 public static class EndOfChain
    {
        public static IPurchaseApprover Instance
        {
            get { return new Denier(); }
        }
    }

    public class Denier : IPurchaseApprover
    {
        public Response ConsiderRequest(IPurchaseRequest request)
        {
            return Response.Denied;
        }

        public void SetNextSupervisor(IPurchaseApprover approver)
        {
            throw new NotImplementedException();
        }
    }
class Program
    {
        static void Main(string[] args)
        {
            var cncRequest = new PurchaseRequest(120000);
            cncRequest.Name = "CNC";

            IPurchaseApprover chief = new Supervisor("Chief Tom", 1000);
            IPurchaseApprover cto = new Supervisor("CTO Mark", 50000);
            IPurchaseApprover owner = new Supervisor("Owner Mike", 500000);
            
            chief.SetNextSupervisor(cto);
            cto.SetNextSupervisor(owner);

            Console.WriteLine("We need to buy CNC");
            var resp = chief.ConsiderRequest(cncRequest);
            if(resp == Response.Granted)
                Console.WriteLine("OK, Let's do it");
            else
            {
                Console.WriteLine("We do not need it");
            }
            Console.ReadKey();

        }
    }