使用相同行为同步所有控件的事件

时间:2021-03-08 15:52:33

in my WPF application I have several forms with a multitude of input fields for the user. As it turns out not every user needs all fields depending on its companie's process therefore I have the new requirement to allow the user to hide fields depending on its own needs.

在我的WPF应用程序中,我有几种形式,为用户提供了大量输入字段。事实证明并非每个用户都需要所有字段,具体取决于其公司的流程,因此我有新的要求允许用户根据自己的需要隐藏字段。

I was planning to use a Behavior for this which could be attached to basically every WPF-Control. The behavior would add a ContextMenu to each of the controls allowing to show/hide all of the available Fields. My current test project which seems to work nice has four DependencyProperties to make everything work:

我计划使用一个行为,这可以基本上附加到每个WPF控件。该行为将向每个控件添加一个ContextMenu,允许显示/隐藏所有可用的字段。我当前的测试项目似乎运行良好有四个DependencyProperties使一切工作:

  1. string VisibilityGroupName:
  2. 字符串VisibilityGroupName:

This acts somehow as an Id for each field but is not unique in order to group multiple fields togther (eG a Label for a field caption to its corresponding TextBox). This string is currently also used as the name that the user sees in the ContextMenu.

这在某种程度上作为每个字段的Id,但不是唯一的,以便将多个字段组合起来(eG将字段标题的标签添加到其对应的TextBox)。此字符串当前也用作用户在ContextMenu中看到的名称。

  1. Dictionary VisibilityDictionary:
  2. 字典可见性字典:

This dictionary keeps track of all the visibility states of the fields. In my application I'm going to serialize this to XML in order to make the users decisions persistant.

该字典跟踪字段的所有可见性状态。在我的应用程序中,我将序列化为XML,以使用户决策持久。

  1. bool AllowCustomVisibility:
  2. bool AllowCustomVisibility:

This is just a flag in order to switch the whole funtionality off.

这只是一个标志,以便关闭整个功能。

  1. bool NotificationDummy:
  2. bool NotificationDummy:

This is where it gets interesting. Currently I abuse this property in conjunction with an ValueChanged-event to notify all the Controls that a state was changed so they can check if they are affected. While this works as expected I know that this is just a bad workaround because I don't know how the notification would be done correctly.

这是它变得有趣的地方。目前,我将此属性与ValueChanged事件一起滥用,以通知所有控件状态已更改,以便他们可以检查它们是否受到影响。虽然这可以按预期工作,但我知道这只是一个糟糕的解决方法,因为我不知道如何正确地完成通知。

Does anybody have an idea how it is done correctly? I've marked the corresponding places in the code with TODOs:

有人知道它是如何正确完成的吗?我用TODO标记了代码中的相应位置:

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 class WPFCustomVisibilityBehavior : Behavior<DependencyObject>
    {
        #region Fields
        private Control _control = null;
        private ContextMenu _contextMenu;
        private bool _contextMenuIsBuilt = false;
        #endregion

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

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

        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()
        {
            // TODO: There should be a better way to notify other controls about state changes than this...
            var temp = DependencyPropertyDescriptor.FromProperty(WPFCustomVisibilityBehavior.NotificationDummyProperty, typeof(WPFCustomVisibilityBehavior));
            if (temp != null)
            {
                temp.AddValueChanged(this, OnNotificationDummyChanged);
            }
        }
        #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;

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

                // TODO: There should be a better way to notify other controls about state changes than this...
                this.NotificationDummy = !NotificationDummy;
            }
        }

        private void OnNotificationDummyChanged(object sender, EventArgs args)
        {
            // TODO: There should be a better way to notify other controls about state changes than this...
            if (this._control != null && this.VisibilityDictionary != null && !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
    }
}

1 个解决方案

#1


0  

Due to the lack of any other ideas I decided to use a static Event which seems to solve my problem quite well and this approach at leasts saves me the NotificationDummy-DependencyProperty which I had to use in the first place.

由于缺乏任何其他想法,我决定使用静态事件,这似乎很好地解决了我的问题,这种方法至少节省了我必须首先使用的NotificationDummy-DependencyProperty。

If anybody is interested - here is my final solution:

如果有人感兴趣 - 这是我的最终解决方案:

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

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
}

}

}

#1


0  

Due to the lack of any other ideas I decided to use a static Event which seems to solve my problem quite well and this approach at leasts saves me the NotificationDummy-DependencyProperty which I had to use in the first place.

由于缺乏任何其他想法,我决定使用静态事件,这似乎很好地解决了我的问题,这种方法至少节省了我必须首先使用的NotificationDummy-DependencyProperty。

If anybody is interested - here is my final solution:

如果有人感兴趣 - 这是我的最终解决方案:

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

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
}

}

}