DarioSantarelli.Blog("UgiDotNet");

<sharing mode=”On” users=”*” />
posts - 176, comments - 105, trackbacks - 3

My Links

News


This is my personal blog. These postings are provided "AS IS" with no warranties, and confer no rights.




Tag Cloud

Archives

Post Categories

My English Blog

Da WindowsForms a WPF via MVP

Qualche giorno fa mi sono imbattutto in una simpatica applicazione Windows Forms che permette di disegnare su una lavangna virtuale il posizionamento in campo di due squadre di calcio. Un'applicazione per allenatori insomma :D. In breve, il funzionamento è semplicemente basato sull'aggiunta e sullo spostamento di pedine (rosse o blu) su un campo di calcio virtuale con eventuale possibilità di ingrandirne/rimpicciolirne le dimensioni.
Guardando il sorgente ho osservato come l’intera applicazione fosse stata concepita e sviluppata "alla Windows Forms" con l'intera logica piazzata all'interno di ciascun event handler dei vari elementi dell'UI.
Ho quindi pensato di esercitarmi nel fare un po' di refactoring, ma soprattutto di analizzare alcuni aspetti prettamente grafici nel porting di una applicazione WindowsForms ad una versione WPF.
Dal punto di vista architetturale, prima di passare alla versione MVVM ho anzitutto ritenuto opportuno inserire un “passaggio intermedio” nel refactoring, soffermandomi su una versione MVP. Il tutto per mettere sostanzialmente in risalto al mio cervello i principali punti di differenza tra i due pattern. Inoltre, come già accennato, la fase di analisi ha toccato alcune potenzialità grafiche di WPF, come il Drag & Drop/Move e lo Zoom.

In questo post vorrei condividere con voi come mi sono mosso, sperando di ricevere magari qualche utile consiglio ;D

Anzitutto sono partito definendo un mini-core, ovvero un'interfaccia "marker" IView per le View

public interface IView { }


ed una classe generica astratta per il presenter:

public abstract class PresenterBase<T> where T : IView
{
 
public T View { get; set; }
}


A questo punto ho iniziato il porting della Form principale dell'applicazione di partenza (che si chiamava SoccerBlackBoardForm) realizzando la corrispondente Window WPF (SoccerBlackBoardWindow) che implementa un'interfaccia IBlackboardView dedotta analizzando il codice dell'applicazione originale. Infine ho realizzato una classe presenter BlackboardPresenter per le View che implementano IBlackboardView. Il tutto è rappresentato sinteticamente nel seguente class Diagram:

Omettendo per brevità il codice dell'interfaccia IBlackboardView, riporto direttamente lo scheletro del mio refactoring dal momento che si tratta di codice abbastanza autoesplicativo:

1. La costruzione della Window SoccerBlackBoardWindow ha letteralmente svuotato di logica la corrispondente versione Windows Forms fino ad avere la seguente forma:

SoccerBlackBoardWindow.xaml

<Window ...>  

    <Grid>

        <Grid.ColumnDefinitions>

            <ColumnDefinition Width="100"></ColumnDefinition>

            <ColumnDefinition Width="*"></ColumnDefinition>

        </Grid.ColumnDefinitions>

        <StackPanel Grid.Column="0" Orientation="Vertical">                    

            <ListBox x:Name="lstImages">

                <ListBox.ItemTemplate>

                    <DataTemplate>

                        <Image ... Source="{Binding}"></Image>

                    </DataTemplate>

                </ListBox.ItemTemplate>

            </ListBox>           

            <Button Content="Add Image >>" Click="AddImageClick" ...></Button>

            <Button Content="Clear" Click="ClearClick" ...></Button>

        </StackPanel>

        <Canvas x:Name="drawingArea" Grid.Column="1" ...>

            <Canvas.Background>

                <ImageBrush Stretch="Fill" 
                            ImageSource
="Resources/SoccerField.png"></ImageBrush>

            </Canvas.Background>

        </Canvas>

    </Grid>

</Window>


SoccerBlackBoardWindow.xaml.cs

public partial class SoccerBlackBoardWindow : Window, IBlackboardView
{
 
private BlackboardPresenter _presenter;

 
#region IBlackboardView Members

 
public Panel DrawingArea
 
{
   
get { return drawingArea; }
 
}

 
public Point MousePosition
 
{
   
get { return Mouse.GetPosition(drawingArea); }          
 
}

 
public IList<BitmapSource> AvailableImages
 
{
   
get { return (IList<BitmapSource>)lstImages.ItemsSource; }
   
set { lstImages.ItemsSource = value; }
 
}

 
public BitmapSource SelectedImage
 
{
   
get { return (BitmapSource)lstImages.SelectedItem; }
   
set { lstImages.SelectedItem = value; }
 
}

 
public void AttachPresenter(BlackboardPresenter presenter) { _presenter = presenter; }

 

  #endregion

 

  public SoccerBlackBoardWindow()

  {

    InitializeComponent();

    _presenter = new BlackboardPresenter(this);

    _presenter.LoadImages();
 
}  

    

  protected void AddImageClick(object sender, RoutedEventArgs e) { _presenter.DrawImage(SelectedImage, new Size(100, 100), 0, 0, 0); }

 
protected void ClearClick(object sender, RoutedEventArgs e) { _presenter.ClearDrawingArea(); }     

}


2. Il presenter BlackboardPresenter è stato quindi realizzato per aggiornare la View sia in base agli eventi utente e l'interazione con il model, che in questo caso è dato da una banale collezione readonly di immagini che l'utente ha a disposizione per l'inserimento nella area di disegno (DrawingArea)
Notare come sia semplice realizzare un meccanismo di Drag & Move :D.

BlackboardPresenter.cs

public class BlackboardPresenter : PresenterBase<IBlackboardView>
{
 
private Point _tmpPosition;
 
private const int ZOOMDELTA = 10;

 
public BlackboardPresenter(IBlackboardView view) { View = view; }

 
public void LoadImages()
 
{           
   
IList<BitmapSource> availableImages = new List<BitmapSource>();           
   
availableImages.Add(new BitmapImage(new Uri("Resources/SoccerBall.png", UriKind.Relative)));
    availableImages.Add(new BitmapImage(new Uri("Resources/blueBall.png", UriKind.Relative))); 
   
availableImages.Add(new BitmapImage(new Uri("Resources/redBall.png", UriKind.Relative)));
   
View.AvailableImages = availableImages;
 
}

 
public void ClearDrawingArea(){ View.DrawingArea.Children.Clear(); }

 
public Image DrawImage(ImageSource source, Size size, int canvasLeft, int canvasTop,int zIndex)       

  {

    Image image = new Image();

           

    image.Source = source;           

    image.Height = size.Height;

    image.Width = size.Width;

 

    image.Cursor = Cursors.Hand;

 

    Canvas.SetLeft(image, canvasLeft);

    Canvas.SetTop(image, canvasTop);

    Panel.SetZIndex(image, zIndex);

 

    image.MouseMove += delegate(object o, MouseEventArgs args) { MoveImage(image); };

    image.MouseWheel += delegate(object o, MouseWheelEventArgs args) { ZoomImage(image, args.Delta); };

    image.MouseDown += delegate(object o, MouseButtonEventArgs args) { CaptureImage(image); };

    image.MouseUp += delegate(object o, MouseButtonEventArgs args) { ReleaseImage(image); }; 

          

    View.DrawingArea.Children.Add(image);

 

    return image;

  }

 

   public void ReleaseImage(Image image) { image.ReleaseMouseCapture(); }

 

   public void CaptureImage(Image image)

   {

     image.CaptureMouse();

     _tmpPosition= View.MousePosition;

   }

 

   public void MoveImage(Image image)

   {           

     if (Mouse.LeftButton == MouseButtonState.Pressed)

     {

       Canvas.SetLeft(image, Canvas.GetLeft(image) + (View.MousePosition.X - _tmpPosition.X));

       Canvas.SetTop(image, Canvas.GetTop(image) + (View.MousePosition.Y - _tmpPosition.Y));

     }

     _tmpPosition= View.MousePosition;

   }

 

   public void ZoomImage(Image image, int amount)

   {

     if (amount > 0)

     {

       image.Height += ZOOMDELTA;

       image.Width += ZOOMDELTA;

     }

     else

     {

       if ((image.Height - ZOOMDELTA > ZOOMDELTA) && (image.Width - ZOOMDELTA > ZOOMDELTA))

       {

         image.Height -= ZOOMDELTA;

         image.Width -= ZOOMDELTA;

       }

     }

   }      

}


Il risultato ottenuto è raffigurato qua sotto: non vi dico quanto è stato divertente usare questa app per spiegare alla mia compagna la tattica del fuorigioco :D.

  

Technorati Tag: ,

Print | posted on venerdì 27 febbraio 2009 21:43 | Filed Under [ WPF ]

Comments have been closed on this topic.

Powered by:
Powered By Subtext Powered By ASP.NET