Laden...

WPF Behavior: Eigener Even ähnlich ValueChanged für DependencyProperty

Erstellt von MillionsterNutzer vor 8 Jahren Letzter Beitrag vor 8 Jahren 1.046 Views
M
MillionsterNutzer Themenstarter:in
235 Beiträge seit 2005
vor 8 Jahren
WPF Behavior: Eigener Even ähnlich ValueChanged für DependencyProperty

Hi!

Sorry für die nichtssagende Überschrift, aber ich weiß schlichtweg nicht wie das heißt has ich suche, daher muss ich hier leider etwas ausholen:

In meiner Anwendung gibt es mehrere Formulare welche meistens nach dem selben Schema aufgebaut sind: Für jeden Wert den der Nutzer bearbeiten kann gibt es ein Label mit einer Überschrift und eine TextBox (oder Ähnlich) um den eigentlichen Wert zu ändern.
Die neue Anfoderung ist es nun das der Nutzer die Möglichkeit bekommen soll einzelne Werte ausblenden zu können (nicht jeder Kunde benutzt alle Felder).

Um dieses Problem felxibel zu lösen würde ich gerne ein Behavior verwenden welches ich an alle Controls hängen kann die ausgeblendet werden sollen. Dieses Behavior besteht hauptsächlich aus zwei Properties: Einen Gruppennamen der bei allen Controls gleich ist welche zusammen gehören (also z.B. ein Label mit der Überschrift + die zugehörige Textbox). Außerdem ein Dictionary welches für alle Gruppennamen eines Formulars einen bool-Wert bereit hält welcher die Sichtbarkeit steuern soll.
Innherhalb des Behaviors wird für jedes Control ein ContextMenü angelegt mit dem man eine Übersicht aller Gruppen bekommt und mit dem man einzelne Gruppen an- und abwählen, also sichtbar und unsichtbar machen kann.

Das Ganze funktioniert auch inzwischen bis zu dem Punkt an dem der Benutzer im Kontextmenü eine Gruppe an- oder abwählt. An dieser Stelle müssen nun eigentlich alle Controls getriggert werden so dass sie das Dictionary mit den Gruppennamen nochmals prüfen ob sich ihr Zustand geändert hat. Ich suche also sowas wie einen Event konnte aber im Netz nicht herausfinden wie sowas auszusehen hat. Im Prinzip würde mir reichen wenn mir jemand ein gutes Stichwort gibt oder ein passendes Tutorial verweist.

Hier noch der Code meines Behavior mit einem TODO an der entsprechenden Stelle:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;

namespace CustomizableUserControlVisibility
{
    public class WPFCustomVisibilityBehavior : Behavior<DependencyObject>
    {
        #region Fields
        private Control _control = null;
        private ContextMenu _contextMenu;
        private bool _contextMenuIsBuilt = false;
        #endregion

        #region Properties
        public bool AllowCustomVisibility
        {
            get { return (bool)GetValue(AllowCustomVisibilityProperty); }
            set { SetValue(AllowCustomVisibilityProperty, value); }
        }

        public static readonly DependencyProperty AllowCustomVisibilityProperty = DependencyProperty.Register("AllowCustomVisibility", typeof(bool), typeof(WPFCustomVisibilityBehavior), new PropertyMetadata(false));

        public string VisibilityGroupName
        {
            get { return (string)GetValue(VisibilityGroupNameProperty); }
            set { SetValue(VisibilityGroupNameProperty, value); }
        }

        public static readonly DependencyProperty VisibilityGroupNameProperty = DependencyProperty.Register("VisibilityGroupName", typeof(string), typeof(WPFCustomVisibilityBehavior), new PropertyMetadata(string.Empty));

        public Dictionary<string, bool> VisibilityDictionary
        {
            get { return (Dictionary<string, bool>)GetValue(VisibilityDictionaryProperty); }
            set { SetValue(VisibilityDictionaryProperty, value); }
        }

        public static readonly DependencyProperty VisibilityDictionaryProperty = DependencyProperty.Register("VisibilityDictionary", typeof(Dictionary<string, bool>), typeof(WPFCustomVisibilityBehavior), new PropertyMetadata(new Dictionary<string, bool>()));
        #endregion

        #region Constructor

        #endregion

        #region Overrrides
        protected override void OnAttached()
        {
            base.OnAttached();

            if (this.AllowCustomVisibility == false)
            {
                return;
            }

            this._control = this.AssociatedObject as Control;

            if (!string.IsNullOrEmpty(this.VisibilityGroupName) && this._control != null)
            {
                if (this.VisibilityDictionary.ContainsKey(this.VisibilityGroupName))
                {
                    if (this.VisibilityDictionary[this.VisibilityGroupName])
                    {
                        this._control.Visibility = Visibility.Visible;
                    }
                    else
                    {
                        this._control.Visibility = Visibility.Collapsed;
                    }
                }
                else
                {
                    this.VisibilityDictionary.Add(this.VisibilityGroupName, this._control.Visibility == Visibility.Visible ? true : false);
                }

                // Add a ContextMenu to the Control, but only if it does not already have one (TextBox brings its default ContextMenu for copy, cut and paste)
                if (this._control.ContextMenu == null && !(this._control is TextBox))
                {
                    this._contextMenu = new ContextMenu();
                    ContextMenuService.SetContextMenu(this._control, this._contextMenu);
                    this._control.ContextMenuOpening += (sender, e) => { ContextMenuOpening(e); };
                }
            }
        }
        #endregion

        #region Event handling
        private void ContextMenuOpening(ContextMenuEventArgs e)
        {
            if (this._contextMenuIsBuilt == false)
            {
                this._contextMenu.Items.Clear();

                Dictionary<string, MenuItem> menuItems = new Dictionary<string, MenuItem>();

                foreach (string k in this.VisibilityDictionary.Keys)
                {
                    MenuItem menuItem = new MenuItem() { Header = k, Name = k, IsCheckable = true, StaysOpenOnClick = true };
                    menuItem.Click += MenuItem_Click;

                    menuItems.Add(k, menuItem);
                }

                var keyList = menuItems.Keys.ToList();
                keyList.Sort();

                foreach (string key in keyList)
                {
                    this._contextMenu.Items.Add(menuItems[key]);
                }

                this._contextMenuIsBuilt = true;
            }

            foreach (MenuItem mi in this._contextMenu.Items)
            {
                mi.IsChecked = this.VisibilityDictionary[mi.Name];
            }
        }

        private void MenuItem_Click(object sender, RoutedEventArgs e)
        {
            MenuItem mi = sender as MenuItem;

            this.VisibilityDictionary[this.VisibilityGroupName] = mi.IsChecked;

            //TODO: Let all suscribing controls know that the Visibility for a group was changed in the VisibilityDictionary => this should call GroupVisibilityWasChanged()...
        }

        private void GroupVisibilityWasChanged()
        {
            if (this.VisibilityDictionary[this.VisibilityGroupName])
            {
                this._control.Visibility = Visibility.Visible;
            }
            else
            {
                this._control.Visibility = Visibility.Collapsed;
            }
        }
        #endregion
    }
}


5.299 Beiträge seit 2008
vor 8 Jahren

ich finde das komisch, es mit einem Behavior zu versuchen.

Für mein Geschmack wäre das zunächstmal ein Fall für eine Property der Viewmodel-Daten: IsDisplayed oder sowas.
Da muss ja auch iwie ein Konfigurations-modell bestehen, und ist wohl auch abzuspeichern, je nach Kunden, oder?

Der frühe Apfel fängt den Wurm.

M
MillionsterNutzer Themenstarter:in
235 Beiträge seit 2005
vor 8 Jahren

Der Hintergrund ist eben auch der dass ich eben mehrere Formulare mit vielen Feldern habe. Wenn ich da in jedem ViewModel für jedes Feld ein extra Property habe was ich ggf. dann auch noch separat speichern würde... das würde ganz schön in Fleißarbeit ausarten.
Mir scheint das Behavior hier einiges (wenn auch bei weitem nicht alles) an Arbeit abzunehmen.
Das Konfigurationsmodell lass ich hier mal aussen vor: Das angesprochene Dictionary welches für jedes Feld die Sichtbarkeit bereithält wird im ViewModel als Property bereitgestellt und im Model in XMl serialisiert/deserialisiert (benutzerbezogen). Da habe ich gar kein Problem.

Ich könnte auch einfach ein zusätzliches Dummy-Property in dem Behavior hinzufügen auf welches ich dann einen ValueChanged-Event setze. Wenn also Ein Feld an oder abgewählt wird könnte ich dieses Dummy-Property ändern und alle anderen würden mitbekommen dass sie nochmal in das Dictionary schauen müssen on sich ihr Zustand geändert hat. Würde funktionieren, scheint mir aber mehr nicht der richtige Weg zu sein.

M
MillionsterNutzer Themenstarter:in
235 Beiträge seit 2005
vor 8 Jahren

Mangels besserer Ideen habe mich heute dazu durchgerungend mein Problem mit einem statischen Event zu lösen - das ist eigentlich ziemlich nahe an dem dran was ich gesucht habe und erspart mir zumindest das Dummy-DependencyProperty das ich in meinem ersten Ansatz noch hatte:


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;

namespace CustomizableUserControlVisibility
{
    public delegate void VisibilityChangedEventHandler(object visibilityDictionary);

    public class WPFCustomVisibilityBehavior : Behavior<DependencyObject>
    {
        #region Fields
        public static event VisibilityChangedEventHandler OnVisibilityChanged;

        private Control _control = null;
        private ContextMenu _contextMenu;
        private bool _contextMenuIsBuilt = false;
        #endregion

        #region Properties
        public bool AllowCustomVisibility
        {
            get { return (bool)GetValue(AllowCustomVisibilityProperty); }
            set { SetValue(AllowCustomVisibilityProperty, value); }
        }

        public static readonly DependencyProperty AllowCustomVisibilityProperty = DependencyProperty.Register("AllowCustomVisibility", typeof(bool), typeof(WPFCustomVisibilityBehavior), new PropertyMetadata(false));

        public string VisibilityGroupName
        {
            get { return (string)GetValue(VisibilityGroupNameProperty); }
            set { SetValue(VisibilityGroupNameProperty, value); }
        }

        public static readonly DependencyProperty VisibilityGroupNameProperty = DependencyProperty.Register("VisibilityGroupName", typeof(string), typeof(WPFCustomVisibilityBehavior), new PropertyMetadata(string.Empty));

        public Dictionary<string, bool> VisibilityDictionary
        {
            get { return (Dictionary<string, bool>)GetValue(VisibilityDictionaryProperty); }
            set { SetValue(VisibilityDictionaryProperty, value); }
        }

        public static readonly DependencyProperty VisibilityDictionaryProperty = DependencyProperty.Register("VisibilityDictionary", typeof(Dictionary<string, bool>), typeof(WPFCustomVisibilityBehavior), new PropertyMetadata(new Dictionary<string, bool>()));
        #endregion

        #region Constructor
        public WPFCustomVisibilityBehavior()
        {
            OnVisibilityChanged += VisibilityChanged;
        }
        #endregion

        #region Overrrides
        protected override void OnAttached()
        {
            base.OnAttached();

            if (this.AllowCustomVisibility == false)
            {
                return;
            }

            this._control = this.AssociatedObject as Control;

            if (!string.IsNullOrEmpty(this.VisibilityGroupName) && this._control != null)
            {
                if (this.VisibilityDictionary.ContainsKey(this.VisibilityGroupName))
                {
                    if (this.VisibilityDictionary[this.VisibilityGroupName])
                    {
                        this._control.Visibility = Visibility.Visible;
                    }
                    else
                    {
                        this._control.Visibility = Visibility.Collapsed;
                    }
                }
                else
                {
                    this.VisibilityDictionary.Add(this.VisibilityGroupName, this._control.Visibility == Visibility.Visible ? true : false);
                }
            }

            // Add a ContextMenu to the Control, but only if it does not already have one (TextBox brings its default ContextMenu for copy, cut and paste)
            if (this._control != null && this._control.ContextMenu == null && !(this._control is TextBox))
            {
                this._contextMenu = new ContextMenu();
                ContextMenuService.SetContextMenu(this._control, this._contextMenu);
                this._control.ContextMenuOpening += (sender, e) => { ContextMenuOpening(e); };
            }
        }
        #endregion

        #region Event handling
        private void ContextMenuOpening(ContextMenuEventArgs e)
        {
            if (this._contextMenuIsBuilt == false)
            {
                // Clear Items just to be sure there is nothing in it...
                this._contextMenu.Items.Clear();

                // Create default items first
                MenuItem showAll = new MenuItem() { Header = "Show all optional fields", IsCheckable = false, FontWeight = FontWeights.Bold };
                showAll.Click += MenuItem_ShowAll_Click;
                MenuItem hideAll = new MenuItem() { Header = "Hide all optional fields", IsCheckable = false, FontWeight = FontWeights.Bold };
                hideAll.Click += MenuItem_HideAll_Click;

                // Create field items and sort them by name
                Dictionary<string, MenuItem> menuItems = new Dictionary<string, MenuItem>();
                foreach (string k in this.VisibilityDictionary.Keys)
                {
                    MenuItem menuItem = new MenuItem() { Header = k, Name = k, IsCheckable = true, StaysOpenOnClick = true };
                    menuItem.Click += MenuItem_Click;

                    menuItems.Add(k, menuItem);
                }
                var keyList = menuItems.Keys.ToList();
                keyList.Sort();

                // Now add default items followed by field items
                this._contextMenu.Items.Add(showAll);
                this._contextMenu.Items.Add(hideAll);
                this._contextMenu.Items.Add(new Separator());

                foreach (string key in keyList)
                {
                    this._contextMenu.Items.Add(menuItems[key]);
                }

                this._contextMenuIsBuilt = true;
            }

            foreach (Object o in this._contextMenu.Items)
            {
                MenuItem mi = o as MenuItem;

                if (mi != null && mi.FontWeight != FontWeights.Bold)
                {
                    mi.IsChecked = this.VisibilityDictionary[mi.Name];
                }
            }
        }

        private void MenuItem_Click(object sender, RoutedEventArgs e)
        {
            MenuItem mi = sender as MenuItem;

            if (mi != null && this.VisibilityDictionary != null && this.VisibilityDictionary.ContainsKey(mi.Name))
            {
                this.VisibilityDictionary[mi.Name] = mi.IsChecked;

                OnVisibilityChanged(this.VisibilityDictionary);
            }
        }

        private void MenuItem_HideAll_Click(object sender, RoutedEventArgs e)
        {
            List<string> keys = this.VisibilityDictionary.Keys.ToList<string>();

            foreach (string key in keys)
            {
                this.VisibilityDictionary[key] = false;
            }

            OnVisibilityChanged(this.VisibilityDictionary);
        }
        private void MenuItem_ShowAll_Click(object sender, RoutedEventArgs e)
        {
            List<string> keys = this.VisibilityDictionary.Keys.ToList<string>();

            foreach (string key in keys)
            {
                this.VisibilityDictionary[key] = true;
            }

            OnVisibilityChanged(this.VisibilityDictionary);
        }

        private void VisibilityChanged(object visibilityDictionary)
        {
            if (this._control != null && this.VisibilityDictionary != null && this.VisibilityDictionary == visibilityDictionary && !string.IsNullOrEmpty(this.VisibilityGroupName))
            {
                if (this.VisibilityDictionary.ContainsKey(this.VisibilityGroupName))
                {
                    if (this.VisibilityDictionary[this.VisibilityGroupName])
                    {
                        this._control.Visibility = Visibility.Visible;
                    }
                    else
                    {
                        this._control.Visibility = Visibility.Collapsed;
                    }
                }
            }
        }
        #endregion
    }
}

Ich dachte ich poste das mal hier, falls mal jemand ein ähnliches Problem hat...