Laden...

DataTemplate's Content klaut focus

Erstellt von DiscMaster vor 13 Jahren Letzter Beitrag vor 13 Jahren 1.522 Views
DiscMaster Themenstarter:in
316 Beiträge seit 2006
vor 13 Jahren
DataTemplate's Content klaut focus

Sers zusammen,

ich kann mir nicht vorstellen das dieses Thema noch nicht diskutiert wurde, aber ich habe keine Antwort dazu gefunden, deswegen erzähl ich euch mal wieder meine Sorgen:

ListView hat Items die über ein DataTemplate layoutet sind. Wenn das ListViewItem selected ist, wird ein Rechteck im Hintergrund anders gefärbt, so weit so gut. Jetzt habe ich in dem Template noch einen Radio-Button und eine Textbox. Wenn ich auf eins der beiden klicke erhält das jeweilige Control den Focus, das ListViewItem wird jedoch nicht selected. Wie löse ich das?

Danke,
DiscMaster.

"Flache Hierarchien schaffen! Das muss konkret nicht unbedingt etwas bedeuten, kommt aber immer sehr gut an."
Bernd Stromberg

L
862 Beiträge seit 2006
vor 13 Jahren

Das hat mit den Focus nichts zu tun. Das liegt daran dass ein ListViewItem erst selektiert wird wenn dieses das MouseLeftButtonDown-Event bekommen. Dieses Routed-Event wird allerdings von besagen Controls verschluckt da diese das Event als 'fertig behandelt' erachten. Ein solches Verhalten kann auch durchaus sinnvoll sein. Wenn du es anders haben willst würde ich mir ein spezielles Behavior für das ListBoxItem schreiben (ich glaube ListViewItem leitet davon ab). Dort kannst du dich dann auf das PreviewMouseLeftButtonDown-Event registrieren und von dort aus IsSelected auf true setzen. Das Preview-Event tritt vor dem normalen Event auf und wird per tunneling weitergeletet. D.h. es tritt im ListViewItem bereits auf bevor es die TextBox es bekommt und kann nicht verschluckt werden (wenn das überhaupt versucht werden würde). Wenn dieses Event ganz unten im VisualTree angekommen ist wirde das normale MouseLeftButtonDown-Event gefeuert welches dann per Bubbeling nach oben geleitet wird. Wenn allerdings ein Control angeklickt wurde welches das weiterleiten unterbindet wird es allerdings nie an deinem ListViewItem ankommen. Folge: Keine Selektierung.

Ich hoffe ich konnte helfen.

DiscMaster Themenstarter:in
316 Beiträge seit 2006
vor 13 Jahren

Yoah, grundsätzlich nachvollziehbar und verständlich. Aber allein auf XAML-Seite kann ich das Problem nich lösen?

"Flache Hierarchien schaffen! Das muss konkret nicht unbedingt etwas bedeuten, kommt aber immer sehr gut an."
Bernd Stromberg

L
862 Beiträge seit 2006
vor 13 Jahren

Zumindest weis ich nicht wie. Aber du kannst 'schmutzigen' Code-Behind vermeiden indem du ein wiederverwendbares Behavior schreibst. Nennen wir es mal 'SelectOnPreviewMouseLeftButtonDown'. Evtl. fällt dir ja ein kürzerer Name ein. Der hier ist allerdings eindeutig.
Du schreibst dir einfach eine Klasse in dir du dieses Behavior implementierst (Verhalten habe ich ja schon erklärt). Ich mache für diese Zwecke immer statische Behavior-Klassen, z.B. ListBoxItemBehavior. Wenn das fertig ist kannst du es dann in XAML verwenden:


<ListView.ItemContainerStyle>
<Style ...>
<Setter Property="local:ListBoxItemBehavior.SelectOnPreviewMouseLeftButtonDown" Value="True"/>
</...>

Ein Behavior ist nichts anderes als ein attached DependencyProperty mit speziellen Changed-Handler. Vorteil ist dass sie sich in XAML verwenden lassen. Beispiele findest du bestimmt über google.

DiscMaster Themenstarter:in
316 Beiträge seit 2006
vor 13 Jahren

Also ich hab mich jetz mit der Behavior-Materie beschäftigt, aber das scheint alles darauf hinauszulaufen, dass ich mir Expression Blend anschaffen muss, weil ich sonst nicht von Behavior<T> ableiten kann, liege ich da richtig?

Expression Blend hab ich aber nicht, und irgendeine abgespeckte Freeware ist mir nicht geläufig... ist mir dennoch zu helfen?

Danke
DiscMaster

"Flache Hierarchien schaffen! Das muss konkret nicht unbedingt etwas bedeuten, kommt aber immer sehr gut an."
Bernd Stromberg

L
862 Beiträge seit 2006
vor 13 Jahren

Also ich mach das ohne Behavior<T> (was auch immer das ist). Ich benutze auch kein Expression Blend. Mach dir einfach ein Attached-Property und programmiere dein Verhalten im entsprechenden Changed-Handler.

C
92 Beiträge seit 2008
vor 13 Jahren

Ich hatte genau das gleiche Problem und habe es dank deines Hinweises wiefolgt gelöst:


    /// <summary>
    /// Exposes attached behaviors that can be applied to ListBoxItem objects.
    /// </summary>
    public static class ListBoxItemBehavior
    {
        #region IsSelectingOnPreviewMouseLeftButtonDown

        /// <summary>
        /// Gets the is selecting on preview mouse left button down.
        /// </summary>
        /// <param name="listBoxItem">The list box item.</param>
        /// <returns></returns>
        public static bool GetIsSelectingOnPreviewMouseLeftButtonDown(ListBoxItem listBoxItem)
        {
            return (bool)listBoxItem.GetValue(IsSelectingOnPreviewMouseLeftButtonDownProperty);
        }

        /// <summary>
        /// Sets the is selecting on preview mouse left button down.
        /// </summary>
        /// <param name="listBoxItem">The list box item.</param>
        /// <param name="value">if set to <c>true</c> [value].</param>
        public static void SetIsSelectingOnPreviewMouseLeftButtonDown(
          ListBoxItem listBoxItem, bool value)
        {
            listBoxItem.SetValue(IsSelectingOnPreviewMouseLeftButtonDownProperty, value);
        }


        /// <summary>
        /// If the Property is set to true, the ListBoxItem is selected on ervery MouseLeftButtonDown inside the ListBoxItem.
        /// </summary>
        public static readonly DependencyProperty IsSelectingOnPreviewMouseLeftButtonDownProperty =
            DependencyProperty.RegisterAttached(
            "IsSelectingOnPreviewMouseLeftButtonDown",
            typeof(bool),
            typeof(ListBoxItemBehavior),
            new UIPropertyMetadata(false, OnIsSelectingOnPreviewMouseLeftButtonDownChanged));

        static void OnIsSelectingOnPreviewMouseLeftButtonDownChanged(
          DependencyObject depObj, DependencyPropertyChangedEventArgs e)
        {
            ListBoxItem item = depObj as ListBoxItem;
            if (item == null)
                return;

            if (e.NewValue is bool == false)
                return;

            if ((bool)e.NewValue)
                item.PreviewMouseLeftButtonDown += new System.Windows.Input.MouseButtonEventHandler(item_PreviewMouseLeftButtonDown);
            else
                item.PreviewMouseLeftButtonDown -= new System.Windows.Input.MouseButtonEventHandler(item_PreviewMouseLeftButtonDown);
        }

        static void item_PreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            DependencyObject sourceControl = e.OriginalSource as DependencyObject;

            try
            {
                if (sourceControl != null)
                {
                    ListBoxItem item = getAncestorListBoxItemOfDependencyObject(sourceControl);
                    item.IsSelected = true;
                }
            }
            catch
            {
                // No Parent-ListBoxItem found -> Do nothing
            }
        }

        private static ListBoxItem getAncestorListBoxItemOfDependencyObject(DependencyObject sourceControl)
        {
            if (VisualTreeHelper.GetParent(sourceControl) == null)
                throw new Exception("The Control is not inside of a ListBoxItem.");
            else if (VisualTreeHelper.GetParent(sourceControl) is ListBoxItem)
                return VisualTreeHelper.GetParent(sourceControl) as ListBoxItem;
            else
                return getAncestorListBoxItemOfDependencyObject(VisualTreeHelper.GetParent(sourceControl));
        }

        #endregion
    }

L
862 Beiträge seit 2006
vor 13 Jahren

So in etwa habe ich mir das gedacht.
Wenn ich noch einen Verbesserungsvorschlag machen darf:


        static void item_PreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            DependencyObject sourceControl = e.OriginalSource as DependencyObject;
//Wenn du hier einfach sender nimmst hast du doch schon das ListBoxItem.
//Dann brauchst du nicht umständlich über den VisualTree vom geklickten Objekt aus suchen sondern kannst gleich das eigendliche Objekt nehmen.
//Dadurch dass du dich ja auf das PreviewMouseLeftButtonDown des ListBoxItems registriert hast muss im sender ja auch genau dieses ListBoxItem stehen.

//Folgende Zeile sollte hier also auch reichen:
//((ListBoxItem)sender).IsSelected = true;
//Ich habs allerdings selbst nicht getestet.
            try
            {
                if (sourceControl != null)
                {
                    ListBoxItem item = getAncestorListBoxItemOfDependencyObject(sourceControl);
                    item.IsSelected = true;
                }
            }
            catch
            {
                // No Parent-ListBoxItem found -> Do nothing
            }
        }

        private static ListBoxItem getAncestorListBoxItemOfDependencyObject(DependencyObject sourceControl)
        {
            if (VisualTreeHelper.GetParent(sourceControl) == null)
                throw new Exception("The Control is not inside of a ListBoxItem.");
            else if (VisualTreeHelper.GetParent(sourceControl) is ListBoxItem)
                return VisualTreeHelper.GetParent(sourceControl) as ListBoxItem;
            else
                return getAncestorListBoxItemOfDependencyObject(VisualTreeHelper.GetParent(sourceControl));
        }

C
92 Beiträge seit 2008
vor 13 Jahren

Du hast natürlich vollkommen recht! Daran habe ich nicht gedacht. Dafür bin ich jetzt um einige Erfahrungen mit den VisualTreeHelper reicher 😉

Dank nochmal!