none
Wie greife ich von einer Page aus auf ein Control des MainWindow zu? RRS feed

  • Frage

  • Hallo,

    ich habe ein MainWindow und von dort lade ich Pages.

    Im MainWindow gibt es dieses Control:
    <TextBlock Name="txtInfo" />

    Nun möchte ich von den Pages aus darauf zugreifen, einen Text zuweisen.

    Alles was ich bisher versucht habe war erfolglos,
    drum hoffe ich au helfende Hinweise in diesem Forum.

    Frank


    www.mehlhop.com

    Montag, 16. Dezember 2013 13:56

Antworten

  • Hallo,
    du solltest die Seite instanzieren und dann ein Event abonnieren, welches die Daten übermittelt. Nachfolgend ein Beispiel dazu:

    Als erstes der Code der Page:

            public event EventHandler<StringTransferEventArgs> DataChanged;//Wird ausgelöst, wenn die Daten geändert wurden
    
            private void Button_Click(object sender, RoutedEventArgs e)
            {
                var evt = this.DataChanged;
                if (evt != null)//Ist das Event abonniert?
                    evt(this, new StringTransferEventArgs(tb.Text));//Event auslösen und Daten übertragen
            }
    Die StringTransferEventArgs-Klasse sieht so aus:
        //Zum transportieren der Daten
        public class StringTransferEventArgs : EventArgs
        {
            public string Data { get; set; }
            public StringTransferEventArgs(string s)
            {
                this.Data = s;
            }
        }
    Beim Aufrufen der Seite musst du nun wie folgt vor gehen:
            private void Button_Click(object sender, RoutedEventArgs e)
            {
                Page1 pg = new Page1();
                pg.DataChanged += pg_DataChanged;//Event abonnieren
                frm.Navigate(pg);//Zur Seite navigieren
            }
    
            void pg_DataChanged(object sender, StringTransferEventArgs e)
            {
                tb.Text = e.Data;//Daten abrufen und ausgeben
            }




    Koopakiller [kuːpakɪllɐ] (Tom Lambert)
    Webseite | Code Beispiele | Facebook | Twitter | Snippets   C# ↔ VB.NET Konverter
    Markiert bitte beantwortende Posts als Antwort und bewertet Beiträge. Danke.

    • Als Antwort markiert frank me Freitag, 20. Dezember 2013 14:24
    Montag, 16. Dezember 2013 14:08
    Moderator

Alle Antworten

  • Hallo,
    du solltest die Seite instanzieren und dann ein Event abonnieren, welches die Daten übermittelt. Nachfolgend ein Beispiel dazu:

    Als erstes der Code der Page:

            public event EventHandler<StringTransferEventArgs> DataChanged;//Wird ausgelöst, wenn die Daten geändert wurden
    
            private void Button_Click(object sender, RoutedEventArgs e)
            {
                var evt = this.DataChanged;
                if (evt != null)//Ist das Event abonniert?
                    evt(this, new StringTransferEventArgs(tb.Text));//Event auslösen und Daten übertragen
            }
    Die StringTransferEventArgs-Klasse sieht so aus:
        //Zum transportieren der Daten
        public class StringTransferEventArgs : EventArgs
        {
            public string Data { get; set; }
            public StringTransferEventArgs(string s)
            {
                this.Data = s;
            }
        }
    Beim Aufrufen der Seite musst du nun wie folgt vor gehen:
            private void Button_Click(object sender, RoutedEventArgs e)
            {
                Page1 pg = new Page1();
                pg.DataChanged += pg_DataChanged;//Event abonnieren
                frm.Navigate(pg);//Zur Seite navigieren
            }
    
            void pg_DataChanged(object sender, StringTransferEventArgs e)
            {
                tb.Text = e.Data;//Daten abrufen und ausgeben
            }




    Koopakiller [kuːpakɪllɐ] (Tom Lambert)
    Webseite | Code Beispiele | Facebook | Twitter | Snippets   C# ↔ VB.NET Konverter
    Markiert bitte beantwortende Posts als Antwort und bewertet Beiträge. Danke.

    • Als Antwort markiert frank me Freitag, 20. Dezember 2013 14:24
    Montag, 16. Dezember 2013 14:08
    Moderator
  • Hi Tom,

    vielen Dank für deinen Code.
    Ich habe noch nie mit einem Event-Abo gearbeitet
    und finde es spannend zu probieren.

    Leider klappt es noch nicht richtig.
    Ich bekomme eine Fehlermeldung:

    in der Page (Production.xaml.cs)
    habe ich die Zeile 
    public event EventHandler<StringTransferEventArgs> DataChanged;
    in die Klasse vor den Konstruktor kopiert.
    Fehlermeldung: "Inkonsistenter Zugriff: Feldtyp 'System.EventHandler<BioSyn.Pages.StringTransferEventArgs>' ist weniger zugreifbar als Feld 'BioSyn.Pages.Production.DataChanged' D:\workspace\BioSynt\BioSyn\Pages\Production.xaml.cs"

    Dabei wird der EventName DataChanged blau unterwellt.

    Die Klasse StringTransferEventArgs habe ich im Ordner Helper erstellt.

    Im MainWindow sieht der Aufruf der Pages bis her so aus:

    void CommonHandler(object sender, RoutedEventArgs e)        {
                deactivateMenuItems();
                MenuItem item = e.Source as MenuItem;
                item.Style = (Style)(this.Resources["menuStyleActive"]);
                switch (item.Name)            {
                    case "miProduction":                    content.Source = new Uri("Pages/Production.xaml", UriKind.RelativeOrAbsolute);
                        break;
                    case "miReport":
                        content.Source = new Uri("Pages/Report.xaml", UriKind.RelativeOrAbsolute);
                        break; . . .


    Ich habe es abgeändert zu:

                    case "miProduction":
                        Production production = new Production();
                        production.DataChanged += pg_DataChanged;
                        content.Navigate(production);
                        break; . . .


    Hast du einen Tipp für mich, was ich falsch mache?

    Grüße! ;)
    Frank


    www.mehlhop.com

    Montag, 16. Dezember 2013 16:05
  • Die Fehlermeldung mit dem Inkonsistenten Zugriff taucht auf, wenn ein verwendeter Typ nicht soweit verwendet werden kann, wie man es für seine Aufgabe braüchte. In diesem Fall heißt das, das die Klasse StringTransferEventArgs wahrscheinlich nicht public ist, das Event aber schon. Mache also die Klasse public oder die Klasse und das Event internal.
    Man könnte also auf die Anwendung verweißen und das Event abonnieren. Nur an die Klasse kommt man in diesem Fall nicht mehr heran.

    Hier erfährst du noch etwas mehr zu Events.


    Koopakiller [kuːpakɪllɐ] (Tom Lambert)
    Webseite | Code Beispiele | Facebook | Twitter | Snippets   C# ↔ VB.NET Konverter
    Markiert bitte beantwortende Posts als Antwort und bewertet Beiträge. Danke.

    Montag, 16. Dezember 2013 16:20
    Moderator
  • Hallo Tom,

    danke für die Hilfe!
    Inzwischen arbeitet das Programm zwar wieder, aber den String bekomme ich leider noch nicht von der Page ins MainWindow.

    Den Aufruf habe ich geändert in 

            private void changeInformationLine(String information)
            {
                var evt = this.DataChanged;
                if (evt != null)
                    evt(this, new StringTransferEventArgs(information));
            }

    Ich rufe auf: 

    changeInformationLine("Neue Info's");

    Aber in der Klasse StringTransferEventArgs kommt nichts an.
    Ich habe einen Breakpoint den Konstruktor dieser Klasse gesetzt und der wird nicht erreicht.

    Dass es kein Button_Click ist, wird sicher nicht die Ursache sein?!
    Hast du noch ne Idee?

    Ich habe mir auch deinen Link angeschaut. Darin finde ich was mit delegates. Davon habe ich auch keine Ahnung, aber fehlt der bei mir evtl. noch?

    Frank


    www.mehlhop.com

    Dienstag, 17. Dezember 2013 08:14
  • Hallo,
    setze mal einen Breakpoint bei der changeInformationLine-Methode. Ich nehme an, dass das Event nicht abonniert wurde, dafür ist die if-Abfrage da. Also wird der Konstruktor nie aufgerufen.

    Das Event muss also mit += abonniert wurden sein. Hier ist mal mein Testprojekt, mit dem oben stehenden Beispielcode:
    http://code-13.net/dl/WpfApplication15.zip (für VS 2012)
    Vielleicht kannst du noch einen anderen Unterschied entdecken. Versuche eventuell mal mein Projekt Schrittweise zu debuggen (F11 für Eizelschritt) um den Ablauf nach volziehen zu können.


    Nun ist dir vielleicht schon aufgefallen, das es scheint, das alle Eventhandler folgende Syntax haben:

    void meinEventHandler(object sender, EventArgs e)
    Das liegt daran, das die EventHandler-Klasse das so verlangt. Du kannst aber auch einen eigenen Delegaten erstellen und diesen als Eventhandler nutzen. Der Delegat stellt dabei nur die Syntax dar, wie die Methode aussehen muss.

    In den meisten Fällen lohnt sich so etwas nicht, auch hier nicht wirklich. Darum habe ich auf die EventHandler-Klasse zurück gegegriffen.


    Koopakiller [kuːpakɪllɐ] (Tom Lambert)
    Webseite | Code Beispiele | Facebook | Twitter | Snippets   C# ↔ VB.NET Konverter
    Markiert bitte beantwortende Posts als Antwort und bewertet Beiträge. Danke.

    Dienstag, 17. Dezember 2013 14:55
    Moderator
  • Hi Tom,

    ich sehe, dass der EventHandler in deinem Projekt funktioniert.
    Bei mir bekomme ich es leider nicht zum laufen.
    In meiner changeInformation-Methode bleibt evt null (Breackpoint gestezt).
    Die Methode pg_DataChanged in der MeinWindow wird nie aufgerufen.
    Ich habe, so wie in deinem Beispiel, die Methode auch mal auf ein Button_Click gesetzt, aber auch da das selbe Ergebnis. :(


    www.mehlhop.com

    Mittwoch, 18. Dezember 2013 08:25
  • Hallo,

    wenn es nur darum geht einen Text zuzuweisen so würde ich versuchen dies mit einem Binding auf eine Eigenschaft zu machen.

    Dazu in einem ResourceDictionary (oder wahlweise auch App.xaml) ein Property anlegen

    xmlns:sys="clr-namespace:System;assembly=mscorlib"
    
    [...]
    
    <Application.Resources>
       <sys:String x:Key="MyString">Hallo Welt</sys:String>
    </Application.Resources>

    In den Pages kann man dann per Knopfdruck oder auch im LoadEvent folgendes machen: 

     Application.Current.Resources["MyString"] = "Hallo neue Welt";


    Nun nur noch den TextBlock anbinden und fertig.

    <TextBlock Text="{DynamicResource MyString}"/>


    Eine weitere Alternative die man machen könnte um zum Ziel zu kommen wäre sich einfach das Control aus dem MainWindow zu fischen und zu bearbeiten:

     MainWindow mw = Application.Current.MainWindow as MainWindow;
            if(mw != null)
            {
                mw.MyTextBlock.Text = "Neuer Text";
            }

    Und natürlich im MainWindow: 

    <TextBlock x:Name="MyTextBlock"/>

    Somit könnte man sich den TextBlock auch als statische Instanz merken und jederzeit darauf zugreifen und Eigenschaften verändern.

    Ich persönlich würde versuchen mehr mit den Ressourcen zu arbeiten (also 1. Beispiel), da man den TextBlock dann ziemlich leicht berabeiten kann.

    In WPF gibt es leider immer viele Wege die zum Ziel führen, richtig oder falsch hängt vom Anwendungsfall ab.

    Ich hoffe ich konnte etwas weiterhelfen.

    Grüße

    JP One


    • Bearbeitet JP One Mittwoch, 18. Dezember 2013 13:04 MyTextBlock hat gefehlt
    Mittwoch, 18. Dezember 2013 13:03
  • Hallo,
    warum das Event bei dir nicht geht kann ich auch nicht sagen. Zumindest fällt mir keine weitere mögliche Fehlerquelle ein.

    Zu den 2 Lösungen von JP One:
    Diese sind natürlich möglich, von daher kein Problem diese einzusetzen.
    Aber bei beiden bist du an gewisse Dinge gebunden:

    • Du musst sicherstellen das es die Resource bzw. den TextBlock gibt.
    • Für die 2. Lösung muss der TextBlock im MainWindow liegen.
    • Wenn du den 2. Weg wählst, mache bitte nicht das Control (den textBlock) selbst pubnlic sondern erstelle eine Eigenschaft im Window, die den String direkt entgegen nimmt.

    Beide Lösungen sind nach außen hin nur schwer verwendbar. (Wenn du die Seite in eine DLL packst). Durch ein Event kann der "Endprogrammierer" alles frei gestalten. Er kann muss aber das Event nicht abonnieren.


    Da fällt mir gerade noch eine ganz andere Möglichkeit ein. Über eine Bindung dirket zwischen der Seite und dem Window.
    In der Page1-Klasse habe ich eine DependencyProperty erstellt:

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        MeineProperty = tb.Text;//Property zuweisen
    }
    
    /// <summary>
    /// Ruft den anzuzeigenden String ab oder legt diesen fest.
    /// </summary>
    public string MeineProperty
    {
        get { return (string)GetValue(MeinePropertyProperty); }
        set { SetValue(MeinePropertyProperty, value); }
    }
    public static readonly DependencyProperty MeinePropertyProperty =
        DependencyProperty.Register("MeineProperty", typeof(string), typeof(Page), new PropertyMetadata(string.Empty ));     

    Sonst ist für den Moment kein weiterer Code nötig. tb ist wieder die TextBox zum eingeben eines Strings. [1]
    Im MainWindow erstellen wir nun eine Bindung zum TextBlock:

    //Instazierung und anzeigen der Seite
    Page1 pg = new Page1();
    tb.SetBinding(TextBox.TextProperty, new Binding() { Path = new PropertyPath("MeineProperty"), Source = pg });//Bindung festlegen
    frm.Navigate(pg);//Zur Seite navigieren

    "MeineProperty" ist der Name der registrierten Eigenschaft. Source ist die zu durchsuchende Klasse (pg). Die Text-Property der Ziel-TextBox wird gebunden.
    Durch festlegeung der Mode-Eigenschaft des Binding-Objekts, in welche Richtung die Daten gebunden werden sollen.

    [1] Du kannst natürlich auch die TextBox auf der Seite binden.

    Diese Methode ist dadurch einfacher zu handehaben, weil die Seite wieder auf nichts von außen angewiesen ist. Was das MainWindow braucht, kann es abrufen.


    Obige zweifel mit der Trennung zwischen Seite und Fenster spielen für dein Projekt vielleicht keine große Rolle, bei größeren Projekten kann es aber zu Problemen kommen. Außerdem sind in Windows Store Apps (für Win 8 bzw. 8.1) DynamicResources nicht mehr möglich. (Zumindest nicht in dieser Art und Weiße)


    Koopakiller [kuːpakɪllɐ] (Tom Lambert)
    Webseite | Code Beispiele | Facebook | Twitter | Snippets   C# ↔ VB.NET Konverter
    Markiert bitte beantwortende Posts als Antwort und bewertet Beiträge. Danke.

    Mittwoch, 18. Dezember 2013 13:52
    Moderator
  • Hallo Koopakiller,

    ich muss Dich an einigen Stellen berichtigen.

    Ich habe eine Exe die 2 DLL's implementiert.

    In der einen DLL wird

        private static int? myInt = Application.Current.TryFindResource("MyInt") as int?;

    aufgerufen.

    In der anderen DLL ist in einem ResourceDicitonary diese Resource angelegt und über ein MergedDictionary in die App.xaml aufgenommen.

    Obwohl die Bibliotheken sich untereinander nicht kennen funktioniert dieses Problemlos zur Laufzeit. Zur Designzeit hab ich einen Workaround der dem "myInt" einen Wert zuweist falls "NULL" zurückkommen sollte.

    Falls sich der TextBlock aus dem MainWindow verschieben sollte, so kann man sich mit dem VisualTreeHelper auseinandersetzen und zur Not den VisualTree komplett auseinander nehmen bis das gewünschte Objekt gefunden wurde (bei der Initialisierung eine sehr zeitaufwändige Suche). 

    Grüße

    JP One


    • Bearbeitet JP One Mittwoch, 18. Dezember 2013 14:33
    Mittwoch, 18. Dezember 2013 14:31
  • Und wo hast du mich berichtigt? Du hast einen Workaround gefunden der das Probem umgeht, ok - kein Problem. Wobei ich, Leistungstechnisch gesehen, die Abfrage ob ein Event abonniert ist, einem Aufruf von TryFindResource vorziehen würde. Außedem ist der Resourcenname durch die Funktionen der DLL belegt.

    Auch wenn es über den VisualTreeHelper geht, verschwendet das Teil hier nur Leistung.

    Ich will nicht sagen, das deine Lösungen nicht funktionieren, nur sehe ich Probleme bei der Umsetzung bzw. Leistung.


    Koopakiller [kuːpakɪllɐ] (Tom Lambert)
    Webseite | Code Beispiele | Facebook | Twitter | Snippets   C# ↔ VB.NET Konverter
    Markiert bitte beantwortende Posts als Antwort und bewertet Beiträge. Danke.

    Mittwoch, 18. Dezember 2013 14:44
    Moderator
  • Ich danke Euch allen,
    danke für die Einführung in Event-Abos

    und für die Alternativen dazu.

    Das mit dem Event funktioniert jetzt sehr schön.
    Der Fehler war, dass ich die Page bereits in der XAML aufgerufen habe und daher das Event wohl nicht richtig initialisiert wurde.
    Bemerkt habe ich dies, als das Event nach dem Umherschalten  plötzlich funktionierte.


    www.mehlhop.com

    Freitag, 20. Dezember 2013 14:24