Metainformationen zur Seite
  •  

Unterschiede

Hier werden die Unterschiede zwischen zwei Versionen angezeigt.

Link zu dieser Vergleichsansicht

Beide Seiten der vorigen Revision Vorhergehende Überarbeitung
Nächste Überarbeitung
Vorhergehende Überarbeitung
start:visualstudio2017:programmieren:dotnetgrundlagen:tipps_tricks [2022/09/22 21:40]
wikiadmin [Working with Checkboxes in the WPF TreeView / Arbeiten mit Kontrollkästchen in der WPF TreeView]
start:visualstudio2017:programmieren:dotnetgrundlagen:tipps_tricks [2022/09/23 07:04] (aktuell)
wikiadmin [Working with Checkboxes in the WPF TreeView / Arbeiten mit Kontrollkästchen in der WPF TreeView]
Zeile 442: Zeile 442:
 I must point out one strange, and disappointing, issue. The Aero theme for WPF’s CheckBox control has a problem in .NET 3.5. When it moves from the ‘Indeterminate’ state to the ‘Checked’ state, the background of the box does not update properly until you move the mouse cursor over it. You can see this in the screenshot below: I must point out one strange, and disappointing, issue. The Aero theme for WPF’s CheckBox control has a problem in .NET 3.5. When it moves from the ‘Indeterminate’ state to the ‘Checked’ state, the background of the box does not update properly until you move the mouse cursor over it. You can see this in the screenshot below:
  
-<code C# [enable_line_numbers="true",highlight_lines_extra="0,"]> +{{:start:visualstudio2017:programmieren:tipps_tricks:screenshot_aero.png?400|}}
-</code>+
  
 </WRAP> </WRAP>
Zeile 489: Zeile 488:
 } }
 </code> </code>
 +Der interessanteste Aspekt dieser ViewModel-Klasse ist die Logik hinter der Eigenschaft IsChecked. Diese Logik erfüllt die Anforderungen 2 und 3, die zuvor gesehen wurden. Die IsChecked-Logik des FooViewModel ist unten dargestellt:
  
 <code C# [enable_line_numbers="true",highlight_lines_extra="0,"]> <code C# [enable_line_numbers="true",highlight_lines_extra="0,"]>
Zeile 538: Zeile 538:
 </code> </code>
  
 +Diese Strategie ist spezifisch für die funktionalen Anforderungen, die ich mir selbst auferlegt habe. Wenn Sie andere Regeln haben, wie und wann Elemente ihren Prüfstatus aktualisieren sollten, passen Sie die Logik in diesen Methoden einfach an Ihre Bedürfnisse an.
 +
 +=== TreeView Konfiguration ===
 +Nun ist es an der Zeit zu sehen, wie die TreeView in der Lage ist, Kontrollkästchen anzuzeigen und an das ViewModel zu binden. Dies wird vollständig in XAML realisiert. Die TreeView-Deklaration ist eigentlich recht einfach, wie unten zu sehen ist:
 +
 +<code C# [enable_line_numbers="true",highlight_lines_extra="0,"]>
 +<TreeView 
 +  x:Name="tree"
 +  ItemContainerStyle="{StaticResource TreeViewItemStyle}"
 +  ItemsSource="{Binding Mode=OneTime}"
 +  ItemTemplate="{StaticResource CheckBoxItemTemplate}"
 +  />
 +</code
 +The TreeView’s ItemsSource property is implicitly bound to its DataContext, which inherits a List<FooViewModel> from the containing window. That list only contains one ViewModel object, but it is necessary to put it into a collection because ItemsSource is of type IEnumerable.
 + 
 +TreeViewItem is a container of visual elements generated by the ItemTemplate. In this demo, we assign the following HierarchicalDataTemplate to the tree's ItemTemplate property:
 + 
 +<code C# [enable_line_numbers="true",highlight_lines_extra="0,"]>
 +<HierarchicalDataTemplate 
 +  x:Key="CheckBoxItemTemplate"
 +  ItemsSource="{Binding Children, Mode=OneTime}"
 +  >
 +  <StackPanel Orientation="Horizontal">
 +    <!-- These elements are bound to a FooViewModel object. -->
 +    <CheckBox
 +      Focusable="False" 
 +      IsChecked="{Binding IsChecked}" 
 +      VerticalAlignment="Center"
 +      />
 +    <ContentPresenter 
 +      Content="{Binding Name, Mode=OneTime}" 
 +      Margin="2,0"
 +      />
 +  </StackPanel>
 +</HierarchicalDataTemplate>
 +</code>
 +
 +In dieser Vorlage gibt es mehrere interessante Punkte. Die Vorlage enthält eine CheckBox, deren Eigenschaft Focusable auf false gesetzt ist. Dadurch wird verhindert, dass die CheckBox jemals den Eingabefokus erhält, was zur Erfüllung von Anforderung 4 beiträgt. Sie fragen sich vielleicht, wie wir die Anforderung 5 erfüllen können, wenn die CheckBox nie den Eingabefokus erhält. Wir werden dieses Problem später in diesem Artikel behandeln, wenn wir untersuchen, wie man das Verhalten eines ToggleButtons an ein TreeViewItem anhängen kann.
 +
 +Die IsChecked-Eigenschaft der CheckBox ist an die IsChecked-Eigenschaft eines FooViewModel-Objekts gebunden, aber beachten Sie, dass die Content-Eigenschaft nicht auf irgendetwas gesetzt ist. Stattdessen befindet sich direkt daneben ein ContentPresenter, dessen Inhalt an die Eigenschaft Name eines FooViewModel-Objekts gebunden ist. Wenn Sie auf eine CheckBox klicken, wird standardmäßig der Status der CheckBox umgeschaltet. Durch die Verwendung eines separaten ContentPresenters, anstatt die Content-Eigenschaft der CheckBox zu setzen, können wir dieses Standardverhalten vermeiden. Dies hilft uns, die Anforderungen 6 und 7 zu erfüllen. Wenn Sie auf das Kästchen in der CheckBox klicken, ändert sich der Status des Kontrollkästchens, aber das Klicken auf den benachbarten Anzeigetext ändert sich nicht. In ähnlicher Weise wählt ein Klick auf das Kästchen in der CheckBox dieses Element nicht aus, aber ein Klick auf den benachbarten Anzeigetext schon.
 +
 +Wir werden den ItemContainerStyle des TreeViews im nächsten Abschnitt untersuchen.
 +
 +=== Einen TreeViewItem in einen ToggleButton verwandeln ===
 +Im vorigen Abschnitt haben wir uns schnell eine interessante Frage gestellt. Wenn die CheckBox im TreeViewItem ihre Focusable-Eigenschaft auf false gesetzt hat, wie kann sie dann als Reaktion auf die Leertaste oder die Eingabetaste ihren Prüfstatus umschalten? Da ein Element nur dann Tastendrücke empfängt, wenn es den Tastaturfokus hat, scheint es unmöglich zu sein, die Anforderung 5 zu erfüllen. Denken Sie daran, dass wir die Eigenschaft Focusable der CheckBox auf false setzen mussten, damit die Navigation von Element zu Element in der Baumstruktur nicht mehrere Tastendrücke erfordert.
 +
 +Dies ist ein kniffliges Problem: Wir können nicht zulassen, dass die CheckBox jemals den Eingabefokus hat, da dies die Navigation über die Tastatur negativ beeinflusst, aber wenn das Element, das sie enthält, ausgewählt ist, muss sie irgendwie ihren Prüfstatus als Reaktion auf bestimmte Tastendrücke umschalten. Dies scheinen sich gegenseitig ausschließende Anforderungen zu sein. Als ich auf diess Hindernis stieß, beschloss ich, die WPF-Jünger um Rat zu fragen, und startete diesen Thread. Zu meiner Überraschung war Dr. WPF bereits auf diese Art von Problem gestoßen und hatte eine geniale Lösung entwickelt, die sich leicht in meine Anwendung integrieren ließ. Der gute Doktor schickte mir den Code für eine VirtualToggleButton-Klasse und war so freundlich, mir zu erlauben, ihn in diesem Artikel zu veröffentlichen.
 +
 +Die Lösung des Doktors verwendet das, was John Gossman als "angehängtes Verhalten" (attached behavior) bezeichnet. Die Idee ist, dass Sie eine angehängte Eigenschaft auf ein Element setzen, so dass Sie von der Klasse, die die angehängte Eigenschaft exponiert, Zugriff auf das Element erhalten können. Sobald diese Klasse Zugriff auf das Element hat, kann sie Ereignisse an das Element koppeln und als Reaktion auf das Auslösen dieser Ereignisse das Element Dinge tun lassen, die es normalerweise nicht tun würde. Dies ist eine sehr bequeme Alternative zum Erstellen und Verwenden von Unterklassen und ist sehr XAML-freundlich.
 +
 +In diesem Artikel sehen wir, wie man einem TreeViewItem eine angehängte IsChecked-Eigenschaft gibt, die umschaltet, wenn der Benutzer die Leertaste oder die Eingabetaste drückt. Diese angehängte IsChecked-Eigenschaft ist an die IsChecked-Eigenschaft eines FooViewModel-Objekts gebunden, das wiederum an die IsChecked-Eigenschaft der CheckBox im TreeViewItem gebunden ist. Diese Lösung erweckt den Anschein, dass eine CheckBox ihren Prüfstatus als Reaktion auf die Leertaste oder die Eingabetaste umschaltet, aber in Wirklichkeit wird ihre IsChecked-Eigenschaft als Reaktion auf ein TreeViewItem aktualisiert, das einen neuen Wert an die IsChecked-Eigenschaft des ViewModels über Datenbindung überträgt.
 +
 +Bevor ich fortfahre, sollte ich darauf hinweisen, dass mir völlig klar ist, dass dies verrückt ist. Die Tatsache, dass dies der sauberste Weg ist, eine TreeView von Kontrollkästchen in WPF v3.5 zu implementieren, zeigt mir, dass Microsoft diesen Aspekt der Plattform vereinfachen muss. Bis dahin ist dies jedoch wahrscheinlich der beste Weg, die Funktion zu implementieren.
 +
 +In dieser Demo machen wir nicht von allen Funktionen der VirtualToggleButton-Klasse von Dr. WPF Gebrauch. Sie bietet Unterstützung für mehrere Dinge, die wir nicht benötigen, wie z. B. die Verarbeitung von Mausklicks und die Bereitstellung von Checkboxen mit drei Zuständen. Wir brauchen nur die Unterstützung für die angehängten Eigenschaften IsVirtualToggleButton und IsChecked sowie das Verhalten bei der Tastaturinteraktion, das sie bietet.
 +
 +Hier ist die Callback-Methode für die angehängte IsVirtualToggleButton-Eigenschaft, die es dieser Klasse ermöglicht, Zugriff auf TreeViewItems im Baum zu erhalten:
 +
 +<code C# [enable_line_numbers="true",highlight_lines_extra="0,"]>
 +/// <summary>
 +/// Behandelt Änderungen an der Eigenschaft IsVirtualToggleButton.
 +/// </summary>
 +private static void OnIsVirtualToggleButtonChanged(
 +  DependencyObject d, DependencyPropertyChangedEventArgs e)
 +{
 +    IInputElement element = d as IInputElement;
 +    if (element != null)
 +    {
 +        if ((bool)e.NewValue)
 +        {
 +            element.MouseLeftButtonDown += OnMouseLeftButtonDown;
 +            element.KeyDown += OnKeyDown;
 +        }
 +        else
 +        {
 +            element.MouseLeftButtonDown -= OnMouseLeftButtonDown;
 +            element.KeyDown -= OnKeyDown;
 +        }
 +    }
 +}
 +</code>
 +Wenn ein TreeViewItem sein KeyDown Ereignis auslöst, wird diese Logik ausgeführt:
 +
 +<code C# [enable_line_numbers="true",highlight_lines_extra="0,"]>
 +private static void OnKeyDown(object sender, KeyEventArgs e)
 +{
 +    if (e.OriginalSource == sender)
 +    {
 +        if (e.Key == Key.Space)
 +        {
 +            // ignore alt+space which invokes the system menu
 +            if ((Keyboard.Modifiers & ModifierKeys.Alt) == ModifierKeys.Alt) 
 +                return;
 + 
 +            UpdateIsChecked(sender as DependencyObject);
 +            e.Handled = true;
 +        }
 +        else if (e.Key == Key.Enter && 
 +         (bool)(sender as DependencyObject)
 +         .GetValue(KeyboardNavigation.AcceptsReturnProperty))
 +        {
 +            UpdateIsChecked(sender as DependencyObject);
 +            e.Handled = true;
 +        }
 +    }
 +}
 + 
 +private static void UpdateIsChecked(DependencyObject d)
 +{
 +    Nullable<bool> isChecked = GetIsChecked(d);
 +    if (isChecked == true)
 +    {
 +        SetIsChecked(d, 
 +         GetIsThreeState(d) ? 
 +         (Nullable<bool>)null : 
 +         (Nullable<bool>)false);
 +    }
 +    else
 +    {
 +        SetIsChecked(d, isChecked.HasValue);
 +    }
 +}
 +</code>
 +Die UpdateIsChecked-Methode setzt die angehängte IsChecked-Eigenschaft auf ein Element, das in dieser Demo ein TreeViewItem ist. Das Setzen einer angehängten Eigenschaft auf einem TreeViewItem hat selbst keinen Effekt. Damit die Anwendung diesen Eigenschaftswert verwenden kann, muss er an etwas gebunden sein. In dieser Anwendung ist sie an die IsChecked Eigenschaft eines FooViewModel Objekts gebunden. Der folgende Style wird der ItemContainerStyle Eigenschaft des TreeViews zugewiesen. Er bindet ein TreeViewItem an ein FooViewModel Objekt und fügt das virtuelle ToggleButton Verhalten hinzu, das wir gerade untersucht haben.
 +
 +<code XML [enable_line_numbers="true",highlight_lines_extra="0,"]>
 +<Style x:Key="TreeViewItemStyle" TargetType="TreeViewItem">
 +  <Setter Property="IsExpanded" Value="True" />
 +  <Setter Property="IsSelected" Value="{Binding IsInitiallySelected, Mode=OneTime}" />
 +  <Setter Property="KeyboardNavigation.AcceptsReturn" Value="True" />
 +  <Setter Property="dw:VirtualToggleButton.IsVirtualToggleButton" Value="True" />
 +  <Setter Property="dw:VirtualToggleButton.IsChecked" Value="{Binding IsChecked}" />        
 +</Style>
 +</code>
 +Dieses Teil fügt das gesamte Puzzle zusammen. Beachten Sie, dass die angehängte Eigenschaft KeyboardNavigation.AcceptsReturn für jedes TreeViewItem auf true gesetzt ist, so dass der VirtualToggleButton seinen Prüfstatus als Reaktion auf die Enter-Taste umschaltet. Der erste Setter im Style, der den Anfangswert der IsExpanded-Eigenschaft jedes Elements auf true setzt, stellt sicher, dass Anforderung 8 erfüllt ist.
 +
 +=== CheckBox-Fehler im Aero-Design ===
 +Ich muss auf ein seltsames und enttäuschendes Problem hinweisen. Das Aero-Thema für das CheckBox-Steuerelement von WPF hat ein Problem in .NET 3.5. Wenn es vom Zustand "Unbestimmt" in den Zustand "Geprüft" wechselt, wird der Hintergrund des Kästchens nicht richtig aktualisiert, bis Sie den Mauszeiger darüber bewegen. Sie können dies im folgenden Screenshot sehen:
 +
 +{{:start:visualstudio2017:programmieren:tipps_tricks:screenshot_aero.png?400|}}
 </WRAP> </WRAP>
 </WRAP> </WRAP>
  
 +[[https://www.codeproject.com/Articles/28306/Working-with-Checkboxes-in-the-WPF-TreeView|Original Artike von Josh Smith.]] [[https://www.deepl.com/translator]|Erste Übersetzung mit Deepl.] [[|]]