none
ContentSelector reagiert nicht auf änderung RRS feed

  • Frage

  • Ich versuche gerade die AppBar von meinem App dynamisch den ListBox Einträgen zuzuordnen und sie entsprechend zu ändern. Habe das ganze mit diesem Code versucht:

    <Page.Resources>
        <local:AppBarSelector x:Key="myAppBarSelector"/>
    </Page.Resources>
    
    <Page.BottomAppBar>
        <AppBar>
            <ContentControl Content="{Binding SelectedItem, ElementName=listBox}" ContentTemplateSelector="{StaticResource myAppBarSelector}">
                <ContentControl.Resources>
                    <DataTemplate x:Key="1">
                        <TextBlock Text="Hallo Welt 1" Foreground="White" />
                    </DataTemplate>
    
                    <DataTemplate x:Key="2">
                        <TextBlock Text="Hallo Welt 2" Foreground="White" />
                    </DataTemplate>
                </ContentControl.Resources>
            </ContentControl>
        </AppBar>
    </Page.BottomAppBar>


    public class AppBarSelector : DataTemplateSelector
    {
        protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
        {
            Debug.WriteLine((string)item);
            if (item == null) return base.SelectTemplateCore(item, container);
    
            var contentControl = (ContentControl)container;
            var templateKey = (string)item;
    
            return (DataTemplate)contentControl.Resources[templateKey];
        }
    }

    Leider tut sich dabei gar nichts. Beim start des Apps wird die Funktion einmal aufgerufen aber dann nie wieder (habe das mit Haltepunkt überprüft). Es reagiert einfach nicht darauf wenn ich einen anderen Eintrag in der ListBox anklicke. Was ist das problem bzw. was habe ich falsch gemacht?

    Ich habe noch ein zweites ContentControl welches auch auf Content="{Binding SelectedItem, ElementName=listBox}" reagiert, allerdings auf einan anderen ContentTemplateSelector. Da funktioniert das ganze wunderbar. Hat jemand eine Idee woran das liegen kann?

    • Bearbeitet Cilenco Montag, 29. April 2013 16:46
    Montag, 29. April 2013 16:44

Antworten

  • Natürlich darfst du mehrmals den Content belegen. Natürlich musst du es in ein Stackpanel oder ähnliche Elemente stecken, die mehr als ein Inhaltsobjekt erlaubt.

    Folgendes Funktioniert, habe ich gerade ausprobiert.

     /// <summary>
        /// Eine leere Seite, die eigenständig verwendet werden kann oder auf die innerhalb eines Rahmens navigiert werden kann.
        /// </summary>
        public sealed partial class MainPage : Page, INotifyPropertyChanged
        {
            public MainPage()
            {
                this.InitializeComponent();
                this.DataContext = this;
            }
    
            /// <summary>
            /// Wird aufgerufen, wenn diese Seite in einem Rahmen angezeigt werden soll.
            /// </summary>
            /// <param name="e">Ereignisdaten, die beschreiben, wie diese Seite erreicht wurde. Die
            /// Parametereigenschaft wird normalerweise zum Konfigurieren der Seite verwendet.</param>
            protected override void OnNavigatedTo(NavigationEventArgs e)
            {
            }
    
            private int _SelectedIndex;
    
            public int SelectedIndex
            {
                get { return _SelectedIndex; }
                set { _SelectedIndex = value; NotifyPropertyChanged();}
            }
            
    
            private object _SelectedItem;
            /// <summary>
            /// Gibt das Item an, welches in der Listbox selectiert wurde.
            /// </summary>
            public object SelectedItem
            {
                get { return _SelectedItem; }
                set { _SelectedItem = value; NotifyPropertyChanged(); }
            }
    
            private ObservableCollection<string> _ItemList = new ObservableCollection<string>() { "Hans", "Paula", "Manfred" };
    
            public ObservableCollection<string> ItemList
            {
                get { return _ItemList; }
                set { _ItemList = value; }
            }
            
    
            
    
            /// <summary>
            /// Mehrfach umgewandeltes Ereignis für Eigenschaftsänderungsbenachrichtigungen.
            /// </summary>
            public event PropertyChangedEventHandler PropertyChanged;
            /// <summary>
            /// Benachrichtigt Listener darüber, dass ein Eigenschaftswert geändert wurde.
            /// </summary>
            /// <param name="propertyName">Name der Eigenschaft zum Benachrichtigen von Listenern. Dieser
            /// Wert ist optional und kann automatisch bereitgestellt werden, wenn ein Aufruf von Compilern erfolgt,
            /// die <see cref="CallerMemberNameAttribute"/> unterstützen.</param>
            internal void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
            {
                var eventHandler = this.PropertyChanged;
                if (eventHandler != null)
                {
                    this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                }
            }
        }

    <Page
        x:Class="App3.MainPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:App3"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        x:Name="Page1"
        mc:Ignorable="d">
    
        <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="300" />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <ListBox DataContext="{Binding ElementName=Page1}" ItemsSource="{Binding ItemList}" x:Name="ListBox"  SelectionMode="Single"
                     SelectedIndex="{Binding Path=SelectedIndex, Mode=TwoWay}"
                     SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}" >
                
            </ListBox>
            <StackPanel Grid.Column="1">
                <TextBlock   DataContext="{Binding ElementName=Page1}" Text="{Binding Path=SelectedItem}" />
                <ContentControl  DataContext="{Binding ElementName=Page1}" Content="{Binding Path=SelectedItem}">
                
                </ContentControl>
            </StackPanel>
        </Grid>
        <Page.BottomAppBar>
            <AppBar>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="asdf" />
                    <TextBlock     DataContext="{Binding }" Text="{Binding Path=SelectedItem}" />
                    <ContentControl  DataContext="{Binding }" Content="{Binding Path=SelectedItem}">
    
                    </ContentControl>
                </StackPanel>
            </AppBar>
        </Page.BottomAppBar>
    </Page>
    


    • Bearbeitet UrielMhezzek Mittwoch, 1. Mai 2013 10:18 XML berichtigt.
    • Als Antwort markiert Cilenco Mittwoch, 1. Mai 2013 17:13
    Mittwoch, 1. Mai 2013 10:15

Alle Antworten

  • Das Problem hatte ich auch schon. Du kannst über eine Appbar nicht die Elemente der Seite Referenzieren. Diese scheinen nicht erkannt zu werden.

    Workaround (ohne MVVM):

    - Erstelle in der Seite eine Eigenschaft vom Typ objekt

    - Binde Listbox-SelectedItem an dieses Objekt

    - Löse unbedingt im Setter das NotifyPropertyChanged-Ereignis aus. Dieses musst du per Interface INotifyPropertyChanged vermutlich erst der Seite mit geben, rest des Codes kannst du aus "BindableBase" kopieren (also notwendige Event und Prozedur).

    - Dann bindest du dein Contentcontrol an die Eigenschaft der Seite.

    Ich hoffe das geht auch ohne MVVM. Ich selbst verwende MVVM, da funktioniert es auf jeden Fall.

    Montag, 29. April 2013 23:11
  • Schritt 3 und 4 verstehe ich leider nicht so ganz. Scheint ja doch recht umständlich so zu sein. Wie löst du das denn über MVVM? Geht das da einfacher? Habe mit MVVM leider so gut wie noch gar nicht mit gearbeitet...
    Dienstag, 30. April 2013 03:39
  • Hallo,

    nein, im MVVM wird es unwesentlich einfacher. Normal hast du ja eine Struktur, bei der du im XAML die Oberfläche und in der Code-Behind die Ereignisse und Prozeduren abhandelst. Mit MVVM erzeugst du dazwischen ein Objekt, an das du die ganze Seite Bindest this.datacontext = new MVVMObjekt(). Das macht bestimmt nicht alles einfacher, insbesondere weil das Konzept vorsieht, das du auch alle Kommandos (also Ereignisse der XAML Oberfläche) bindest. Praktisch ist es hingegen, seine Programmierung ordentlich zu halten (und Oberflächenunabhängig zu testen).

    Aber nein, durch MVVM-Pattern wird es nicht zwangsweise einfacher.

    Die Eigenschaft

     private object  _SelectedItem;
            /// <summary>
            /// Gibt das Item an, welches selectiert ist.
            /// </summary>
            public object SelectedItem
            {
                get { return _SelectedItem; }
                set { _SelectedItem = value; NotifyPropertyChanged(); }
            }

    Notwendige Impelemntierung für die NotifyPropertyChanged

            /// <summary>
            /// Mehrfach umgewandeltes Ereignis für Eigenschaftsänderungsbenachrichtigungen.
            /// </summary>
            public event PropertyChangedEventHandler PropertyChanged;
            /// <summary>
            /// Benachrichtigt Listener darüber, dass ein Eigenschaftswert geändert wurde.
            /// </summary>
            /// <param name="propertyName">Name der Eigenschaft zum Benachrichtigen von Listenern. Dieser
            /// Wert ist optional und kann automatisch bereitgestellt werden, wenn ein Aufruf von Compilern erfolgt,
            /// die <see cref="CallerMemberNameAttribute"/> unterstützen.</param>
            internal void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
            {
                var eventHandler = this.PropertyChanged;
                if (eventHandler != null)
                {
                     this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                }
            }
    

     Dann müsste dein Contentcontrol folgendermaßen funktionieren

    <ContentControl Content="{Binding SelectedItem}" ContentTemplateSelector="{StaticResource myAppBarSelector}">
    
    Achte auch immer darauf was der Debugger sagt. Im Fenster namens Ausgabe schreibt er immer alle Binding-Errors. Daran kannst du erkennen, auf welchen Objekt er die Eigenschaft sucht.

     

    Dienstag, 30. April 2013 08:48
  • - Erstelle in der Seite eine Eigenschaft vom Typ Object

    - Binde Listbox-SelectedItem an dieses Objekt

    - Löse unbedingt im Setter das NotifyPropertyChanged-Ereignis aus. Dieses musst du per Interface INotifyPropertyChanged vermutlich erst der Seite mit geben, rest des Codes kannst du aus "BindableBase" kopieren (also notwendige Event und Prozedur).

    - Dann bindest du dein Contentcontrol an die Eigenschaft der Seite.

    Ich gehe jetzt mal die schritte durch:

    1. Hast du mir ja gegeben: private object _SelectedItem;

    2. Habe ich so versucht nicht ganz sicher: <ListView SelectedItem="{Binding: _SelectedItem}" ... /> Kann man das so machen?

    3. Hast du mir ja als Funktion gegeben - muss ich die noch iwie an das Object anhängen?

     4. Hast du mir ja gegeben: <ContentControl Content="{Binding SelectedItem}" ContentTemplateSelector="{StaticResource myAppBarSelector}">

    Habs erstmal nur kopiert aber da tut sich noch nichts. Verstanden habe ich es glaube ich so halb. Muss ich jetzt noch was ergänzen oder ist einfach nur in Schritt 2 falsch gebunden? Kann ich einfach von XAML auf Variablen in der Klasse (Code behind) zugreifen? Oder verwechsel ich jetzt die Namen komplett?


    • Bearbeitet Cilenco Dienstag, 30. April 2013 14:36
    Dienstag, 30. April 2013 14:31
  • Zu 1.) nicht ganz richtig. Evtl solltest du dir noch mal die Grundlagen der Objekteigenschaften anschauen. Hier siehst du wie Eigenschaften funktionieren. http://msdn.microsoft.com/de-de/library/x9fsa0sw(v=vs.80).aspx Egal was du mit Eigenschaften machst, das Private objekt ist Tabu für den Zugriff. Nur der Getter und Setter der Eigenschaft darf darauf zu greifen.

    Zu 2;) so ist es korrekt <ListView SelectedItem="{Binding: SelectedItem}" ... /> (Man beachte, ich habe den Unterstrich _ entfernt).

    Zu 3;) Keine Funktion, eine Eigenschaft. Kann man aber leicht verwechseln.

    Zu 4;)Du kannst in XAML auf im Prinzip auf so ziemlich alles verweisen. Worauf der verweis zielt, liegt beim Binding immer am DataContext, der aber auch aus einen Übergeordneten Element stammen kann. Du kannst jede öffenliche Eigenschaft jedes Objektes Binden. Damit es aber korrekt funktioniert, muss die Eigenschaft das NotifyPropertyChanged Ereignis auslösen, damit die Benutzeroberfläche weiß, das sie etwas tun muss. Daher macht es auch kein Sinn, das private Feld anzusteuern, wie du es probiert hast.

    Falls es nicht klappt schau bitte die Fehlermeldung im "Ausgabefenster" an und schreib sie bitte hier her.

    Es können theoretisch zwei Fehlerausgaben kommen. damit ich aber mehr sagen kann, muss ich wissen wie sie lauten ;).

    Dienstag, 30. April 2013 15:43
  • Sorry habe jetzt lange Zeit in Java programmiert da gibt es Eigenschaften ja nicht deswegen komme ich da im mom noch manchmal durcheinandner ;)

    Ich habe die Eigenschaft mal so umgeschrieben:

    public object SelectedItem
            {
                get { Debug.WriteLine("get"); return _SelectedItem; }
                set { Debug.WriteLine("set"); _SelectedItem = value; NotifyPropertyChanged(); }
            }

    Aber diese Ausgabe kommt auch schon nicht zu stande. Die ListBox sieht so aus:

    <ListBox x:Name="listBox" SelectedItem="{Binding SelectedItem, Mode=TwoWay}" Foreground="Black" BorderThickness="0" Background="#FFD8D8D8" />
    Habe das ganze auch ohne Mode=TwoWay versucht. Auch mit einem Haltpunkt beim Debugger wird die Eigenschaft nicht aufgerufen. Zu sonstigen Ausgaben im Ausgabefenster kommt es nicht. Alles andere ist momentan auskommentiert da das ja der erste schritt in die richtige Richtung wäre.

    • Bearbeitet Cilenco Dienstag, 30. April 2013 16:11
    Dienstag, 30. April 2013 16:04
  • Du musst die Listbox noch mit dem Item Syncronsieren. Ich bin mir grad nicht sicher, ich glaube die Eigenschaft heißt IsSynchronizedWithCurrentItem oder ähnlich. Nur dann wird SelectedItem bzw SelectedIndex gefeuert.
    Dienstag, 30. April 2013 16:43
  • Mit IsSynchronizedWithCurrentItem gab es Fehler, dass man das nicht auf True setzen darf, aber ich habe es jetzt so zum laufen bekommen:

    <ListBox x:Name="listBox" SelectedItem="{Binding ElementName=Site1,Path=SelectedItem, Mode=TwoWay}" Grid.Row="1" Foreground="Black" BorderThickness="0" Background="#FFD8D8D8" />

    Es wird sogar schon NotifyPropertyChanged() ausgeführt. Jetzt habe ich das ContentControl in der AppBar so beschrieben:

    <AppBar>
        <ContentControl x:Name="AppBar" Content="{Binding SelectedItem}" ContentTemplateSelector="{StaticResource myAppBarSelector}">
            <ContentControl.Resources>
                <DataTemplate x:Key="Stundenplan">
                    <TextBlock Text="Hallo Welt 1" Foreground="White" />
                </DataTemplate>
    
                <DataTemplate x:Key="Preferences">
                    <TextBlock Text="Hallo Welt 2" Foreground="White" />
                </DataTemplate>
            </ContentControl.Resources>
        </ContentControl>
    </AppBar>

    aber in der AppBar ändert sich bisher noch nichts. Und die Klasse sieht so aus:

    public sealed partial class MainPage : Page, INotifyPropertyChanged




    • Bearbeitet Cilenco Mittwoch, 1. Mai 2013 08:52
    Dienstag, 30. April 2013 23:49
  • Ok. Schon mal ein Teilerfolg :). Das heißt, die Listbox und dein Codebehind reagieren wie gewünscht. Nun müssen wir noch die Appbar dazu bringen.

    Probiere mal folgende Varianten. Ich denke es liegt daran (habe ich übersehen), das du den DataContext in deinen ContentControl nicht setzt.

                        <TextBlock  DataContext="{Binding Path=SelectedItem}" Text="{Binding}">
    
                        </TextBlock>
                        <ContentControl DataContext="{Binding Path=SelectedItem}"
                                HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
                                VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch"
                                Content="{Binding}"
                                Grid.Column="1" ContentTemplateSelector="{StaticResource EditorContentTemplateSelector}"
                                
                    >
    
                        </ContentControl>

    Wenn dann nicht funktioniert, binde mal den DataContext folgendermassen

     ElementName=Site1,Path=SelectedItem
    Mittwoch, 1. Mai 2013 09:38
  • Ich habe jetzt im ContentControl in der AppBar nur das ergänzt: DataContext="{Binding Path=SelectedItem}" und auch die andere Variante durchprobiert (auch nochmal mit Mode=TwoWay) aber das bringt leider nichts. Mit dem TextBlock darüber wird ja Content 2 mal belegt was nicht sein darf aber ich gehe mal davon aus du hast das einfach aus einem deiner Projekte kopiert?
    • Bearbeitet Cilenco Mittwoch, 1. Mai 2013 09:51
    Mittwoch, 1. Mai 2013 09:50
  • Natürlich darfst du mehrmals den Content belegen. Natürlich musst du es in ein Stackpanel oder ähnliche Elemente stecken, die mehr als ein Inhaltsobjekt erlaubt.

    Folgendes Funktioniert, habe ich gerade ausprobiert.

     /// <summary>
        /// Eine leere Seite, die eigenständig verwendet werden kann oder auf die innerhalb eines Rahmens navigiert werden kann.
        /// </summary>
        public sealed partial class MainPage : Page, INotifyPropertyChanged
        {
            public MainPage()
            {
                this.InitializeComponent();
                this.DataContext = this;
            }
    
            /// <summary>
            /// Wird aufgerufen, wenn diese Seite in einem Rahmen angezeigt werden soll.
            /// </summary>
            /// <param name="e">Ereignisdaten, die beschreiben, wie diese Seite erreicht wurde. Die
            /// Parametereigenschaft wird normalerweise zum Konfigurieren der Seite verwendet.</param>
            protected override void OnNavigatedTo(NavigationEventArgs e)
            {
            }
    
            private int _SelectedIndex;
    
            public int SelectedIndex
            {
                get { return _SelectedIndex; }
                set { _SelectedIndex = value; NotifyPropertyChanged();}
            }
            
    
            private object _SelectedItem;
            /// <summary>
            /// Gibt das Item an, welches in der Listbox selectiert wurde.
            /// </summary>
            public object SelectedItem
            {
                get { return _SelectedItem; }
                set { _SelectedItem = value; NotifyPropertyChanged(); }
            }
    
            private ObservableCollection<string> _ItemList = new ObservableCollection<string>() { "Hans", "Paula", "Manfred" };
    
            public ObservableCollection<string> ItemList
            {
                get { return _ItemList; }
                set { _ItemList = value; }
            }
            
    
            
    
            /// <summary>
            /// Mehrfach umgewandeltes Ereignis für Eigenschaftsänderungsbenachrichtigungen.
            /// </summary>
            public event PropertyChangedEventHandler PropertyChanged;
            /// <summary>
            /// Benachrichtigt Listener darüber, dass ein Eigenschaftswert geändert wurde.
            /// </summary>
            /// <param name="propertyName">Name der Eigenschaft zum Benachrichtigen von Listenern. Dieser
            /// Wert ist optional und kann automatisch bereitgestellt werden, wenn ein Aufruf von Compilern erfolgt,
            /// die <see cref="CallerMemberNameAttribute"/> unterstützen.</param>
            internal void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
            {
                var eventHandler = this.PropertyChanged;
                if (eventHandler != null)
                {
                    this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                }
            }
        }

    <Page
        x:Class="App3.MainPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:App3"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        x:Name="Page1"
        mc:Ignorable="d">
    
        <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="300" />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <ListBox DataContext="{Binding ElementName=Page1}" ItemsSource="{Binding ItemList}" x:Name="ListBox"  SelectionMode="Single"
                     SelectedIndex="{Binding Path=SelectedIndex, Mode=TwoWay}"
                     SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}" >
                
            </ListBox>
            <StackPanel Grid.Column="1">
                <TextBlock   DataContext="{Binding ElementName=Page1}" Text="{Binding Path=SelectedItem}" />
                <ContentControl  DataContext="{Binding ElementName=Page1}" Content="{Binding Path=SelectedItem}">
                
                </ContentControl>
            </StackPanel>
        </Grid>
        <Page.BottomAppBar>
            <AppBar>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="asdf" />
                    <TextBlock     DataContext="{Binding }" Text="{Binding Path=SelectedItem}" />
                    <ContentControl  DataContext="{Binding }" Content="{Binding Path=SelectedItem}">
    
                    </ContentControl>
                </StackPanel>
            </AppBar>
        </Page.BottomAppBar>
    </Page>
    


    • Bearbeitet UrielMhezzek Mittwoch, 1. Mai 2013 10:18 XML berichtigt.
    • Als Antwort markiert Cilenco Mittwoch, 1. Mai 2013 17:13
    Mittwoch, 1. Mai 2013 10:15
  • Was ist denn bei dir das Selectedasdfitem? Das kommt weder im XAML noch im Code behind vor.
    Mittwoch, 1. Mai 2013 10:36
  • Wenn du genau hin schaust ;) habe ich das bereits rauskorriegiert. Damit habe ich getestet, ob es überhaupt Binding-Fehlerausgaben für die AppBar gibt. Was wohl nicht der Fall ist. Da kann man dann tatsächlich lange rumprobieren, bis es klappt.
    Mittwoch, 1. Mai 2013 10:40