Avatar billede haolan Nybegynder
05. november 2008 - 14:09 Der er 8 kommentarer

Kalde event på en thread uden brud på mønster

Hej

Jeg har en lavet en klasse med en tråd jeg kalder ParseThread.
Jeg har gjort meget ud af at overholdet et løst koblet mønster, men har nu fået problemer.

Mit program er strukturetet sådan:

[GUI]

[Facade]

[ParseThread]

Måden det fungerer på er at GUI'en må ikke kende til ParseThread og ParseThread må helst ikke kende til GUI'en.

Systemet bruges til at parse en lang række filer og jeg opdaterer derfor på en statusbar med et event og delegate.

I min GUI klasse har jeg så været nødt til at gøre sådan:

ParseThread parseThread = facade.getParseThread();
parseThread.NumberOfFilesEvent += new ParseThread.MyEventHandler(updateStatus);

Hvor updateStatus er selve den funktion jeg kalder for at performe et step på min progressbar.

UpdateStatus ser sådan ud:

if(lblStatus.InvokeRequired)
  lblStatus.Invoke(new SetTextboxDelegate(updateStatus), new object[] { numberOfFiles });
else
  lblStatus.Text = numberOfFiles + " filer af " + startNumberOfFiles;

if (pgBar.InvokeRequired)
  pgBar.Invoke(new SetTextboxDelegate(updateStatus), new object[] { numberOfFiles });
else
  pgBar.PerformStep();

Som i kan se bryder jeg jo mønsteret ved at min GUI klasse kommer til at kende parsethread for at få fat i eventet.

Hvordan løser jeg dette problem?
Avatar billede hmortensen Nybegynder
05. november 2008 - 14:50 #1
Kan du ikke blot lave en event i facade klassen og og gøre så facade klassen lytter på tråden og rejser sin egen event når der sker noget, så kan gui lytte på den.
Avatar billede haolan Nybegynder
05. november 2008 - 15:03 #2
Det er ihvertfald den bedste løsningsmulighed jeg har fundet endnu..
Avatar billede haolan Nybegynder
05. november 2008 - 15:04 #3
fundet = hørt..

Jeg prøver senere og vender tilbage med svar :)

Med mindre der kommer andre forslag inden self..
Avatar billede taurr Nybegynder
05. november 2008 - 21:33 #4
Det pattern jeg kører efter normalt består af en presenter (din facade) som har et model-lag (i dette tilfælde din parser thread). Presenteren har ligeledes en GUI, men kender kun denne via et simpelt interface, ligesom gui'en kun kender sit datagrundlag som et interface.

Presenteren får fat i gui'en via et normalt builder pattern (jeg ynder at bruge en IOC container til dette, f.eks. Unity eller StructureMap), og injecter en proxy for modellen i gui'en (der kun kender modellen som et interface), der herefter er i stand til at databinde til diverse værdier.

Proxy'en kan sagtens være en del af modellen selv (kommer jo an på hvor meget man eksponerer i interfacet), eller af presenteren for den sags skyld. Ligeledes kunne modellen i simple tilfælde være indeholdt direkte i presenteren. Det essentielle her er, at det er presenteren (eller din facade) som instantierer de andre og sætter bindingerne op imellem dem - modellen (din parser-thread) kender ingen andre, og dit view kender kun til de data der er vigtige for den via et interface... hvilket object der leverer data ved view'et ikke, eftersom det bliver leveret fra presenteren!

Hvis du vil køre dette helt ud, så kan du jo evt. også benytte dig af et Command pattern. Her vil du lade viewet rejse et event med et kommando-objekt hver gang det vil noget der ikke er UI relateret. Presenteren (facaden i dit tilfælde) vil så fortolke kommando objektet og eventuelle data der er knyttet til det, gøre hvad der skal til og ændre data, som jo er databound til UI'en, og cirklen er fuldendt. På denne måde vil UI interfacet bestå af et event (f.eks. ExecuteCommand), og en setable property (f.eks. DataContext) samt en række kommandoer. Viewet kender udelukkende til data grundlaget som et interface, og selve modellen ved egentlig ingenting. Presenteren ved ikke engang hvilket view den bruger eftersom det bliver lavet af en builder der kan have fundet den konkrete klasse i en konfigurations-fil. En fordel ved denne metode er at du faktisk kan gemme kommando-objekterne for på den måde at lave en "makro recording" eller "undo/redo" funktion.

bare mine 1000 ord :-)

mvh Johnny
Avatar billede taurr Nybegynder
05. november 2008 - 22:20 #5
For at gøre mit svar lidt kortere, og måske mere præcist: brug databinding.

Gui'en kender allerede facaden og bruger den jo sikkert til at hente alle sine data fra. Lad Facaden og ParserThread implementere INotifyPropertyChanged...

For at gøre det lidt lettere...

public static class PropertyChangedEventHandlerExtensions
{
  public static void SafeRaise(this PropertyChangedEventHandler eventHandler, object sender, string propertyName)
  {
    if (eventHandler != null)
    {
      foreach (Delegate handler in eventHandler.GetInvocationList())
      {
        ISynchronizeInvoke synchronizer = handler as ISynchronizeInvoke;
        if (synchronizer != null && synchronizer.InvokeRequired)
        {
          Action raiser = () => ((PropertyChangedEventHandler)handler)(sender, new PropertyChangedEventArgs(propertyName));
          synchronizer.Invoke(raiser, null);
        }
        else
        {
          ((PropertyChangedEventHandler)handler)(sender, new PropertyChangedEventArgs(propertyName));
        }
      };
    }
  }
}

I ParserThread:

class ParserThread : INotifyPropertyChanged
{
  public event PropertyChangedEventHandler PropertyChanged;

  private int numberOfFiles; // don't use this directly - use the property!

  public int NumberOfFiles
  {
    get { return numberOfFiles; }
    private set
    {
      numberOfFiles = value;
      OnPropertyChanged("NumberOfFiles");
    }
  }

  protected virtual void OnPropertyChanged(string propertyName)
  {
    PropertyChanged.Raise(propertyName);
  }

  ... og opdater NumberOfFiles som det nu passer sig.
}

I facaden:

class xxxx : INotifyPropertyChanged
{
  private PropertyChangedEventHandler internalChangeHandlers;

  public event PropertyChangedEventHandler PropertyChanged
  {
    add
    {
      parserThread.PropertyChanged += value; // some of our properties are just redelegatet from the parserThread
      internalChangeHandlers += value; // Add eventhandler to own list of data-subscribers
    }
    remove
    {
      parserThread.PropertyChanged -= value; // some of our properties are just redelegatet from the parserThread
      internalChangeHandlers -= value; // Remove eventhandler from own list of data-subscribers
    }
  }

  public int NumberOfFiles
  {
    get { return parserThread.NumberOfFiles; }
  }

  protected virtual void OnPropertyChanged(string propertyName)
  {
    internalChangeHandlers.Raise(propertyName);
  }
}

I View'et:

Bare brug databinding op imod facaden. Viewer vil automatisk blive opdateret, og kender ikke de bagvedliggende mekanismer.

Kan du ikke bruge databinding, kan du altid subscribe til facade.PropertyChanged for at få at vide hvornår og hvilket properties der har ændret sig.
Avatar billede haolan Nybegynder
09. november 2008 - 18:59 #6
jeg er ikke i tvivl om din metode er den prof taurr.. Men jeg skal til eksamen i det snart, så er nødt til at være realistisk med hvad jeg kan nå at sætte mig ind i og jeg tror dit ligger på et rimelig højere niveau end hvad der forventes af mig lige PT..

hmortensen..

Kan du udspecificere lidt nærmere hvordan du ville bygge det op?

PT har jeg jo disse to linjer stående i min parserThread klasse:

public delegate void MyEventHandler(int numberOfFiles);
        public event MyEventHandler NumberOfFilesEvent;

Hvordan får jeg lavet en lytter på facadeklassen istedet?
Avatar billede haolan Nybegynder
09. november 2008 - 19:32 #7
nvm.. Fandt ud af det :)
taurr.. Hvis du kan udspecifivere præcis hvad der sker i den kode og hvor vil jeg meget gerne lige se den, inden jeg deler points ud.

Er ikke sikker på jeg helt er med på hvad din kode helt præcist gør :)
Avatar billede taurr Nybegynder
11. november 2008 - 02:26 #8
Jeg har lavet et lille mini projekt der gerne skulle demonstrere hvad jeg mener. Indrømmet jeg har ikke taget mig tiden til at skrive alle kommentarer som jeg burde, men projektet virker og er nemt at overskue... håber det hjælper.

Du kan finde koden her: http://www.coontown.dk/Portals/coontown/downloads/ThreadFacadeTest.zip

Mvh. Johnny
Avatar billede Ny bruger Nybegynder

Din løsning...

Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.

Loading billede Opret Preview

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester