如何将用户控件动态添加到窗口中

本文关键字:添加 窗口 动态 控件 用户 | 更新日期: 2023-09-27 18:00:40

我有一个从Code Behind中提取并转换为MVVM的程序。这个程序很简单。它从文本框中获取文本,并将字符串转换为管道分层文本文件。我的程序有一个MainViewWindow和3个用户控件,它们使用ModernUI选项卡系统来确定哪个用户控件填充MainViewWindow。我现在只处理第一个用户控件。我使用SimpleIoc(ViewModelLocator)来识别哪个用户控件被选中并在MainViewWindow上处于"活动"状态。用户控件有大约30个绑定到ViewModel的文本框。其中一些文本框有按钮,可以(应该)激活一个弹出窗口,询问其他信息。例如,"Name:"文本框旁边有一个按钮,该按钮应弹出一个窗口,要求在单独的文本框中输入"名字"、"姓氏"、"中间名"、"后缀"answers"前缀"。此信息被解析为单个字符串,并通过"^"符号分隔,然后放回"名称:"文本框中。这并不难,但我想提供肉和土豆。

我的问题是,我有大约8个地方需要一个按钮来弹出"附加信息"窗口。所有这些都需要不同的信息,所以我创建了具有必需字段的用户控件。我正试图在按下按钮时动态添加到这个弹出窗口中。我根本不知道如何弹出一个窗口,并通过命令将用户控件添加到窗口中。这是我基于MVVM框架的研究和整合的逻辑。如果我走在正确的道路上(我当然希望如此!)有人能填补空白吗?如果有更好的方法,请给我指正确的方向?

如何将用户控件动态添加到窗口中

据我所知,您需要创建一个新表单,然后通过按钮调用它。

一旦出现此表单,就可以动态地向其中添加适合该按钮的控件。

这是显示附加表单的按钮代码:

 private void button1_Click(object sender, EventArgs e)
        {
            PopupForm form = new PopupForm();
            form.ShowDialog();
        }

我不会为您提供完整的代码,但要动态创建一个控件并实际使用它,您需要以下信息

Control type 
Control location 
Control size 
Control name

一旦表单加载,它应该调用一些方法来构建控件。这个问题以前已经回答过了:如何创建5个按钮并动态分配单个点击事件?

我个人不喜欢WPF的默认PopupControl,原因有很多,所以我创建了自己的,我认为它更适合MVVM模式

即使你自己创造,基本的想法也是一样的。您将自定义PopupControl放置在视图的顶部,控件允许对象重叠,例如网格,如

<Grid>
    <local:MyView />
    <local:MyPopupControl />
</Grid>

你在弹出控件上挂上了一些绑定

<Grid x:Name="ParentPanel">
    <local:MyView />
    <local:MyPopupControl 
        Content="{Binding PopupContent}"
        local:MyPopupControl.PopupParent="{Binding ElementName=ParentPanel}"
        local:MyPopupControl.IsPopupVisible="{Binding IsPopupVisible}"/>
</Grid>

然后从ViewModel中,您只需设置适当的属性即可使其显示

PopupContent = new AddressViewModel();
IsPopupVisible = true;

然后,神奇的事情发生了,导致弹出菜单出现在主视图的上方,它的内容属性设置为新的ViewModel,使用您定义的DataTemplate绘制,一切都很好。

但说真的,这是我用于自定义PopupControl的代码,以防我的博客链接停止工作。不过,我还是建议你读它作为一个例子。

using System;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Threading;
namespace PopupPanelSample
{
    /// <summary>
    /// Panel for handling Popups:
    /// - Control with name PART_DefaultFocusControl will have default focus
    /// - Can define PopupParent to determine if this popup should be hosted in a parent panel or not
    /// - Can define the property EnterKeyCommand to specifify what command to run when the Enter key is pressed
    /// - Can define the property EscapeKeyCommand to specify what command to run when the Escape key is pressed
    /// - Can define BackgroundOpacity to specify how opaque the background will be. Value is between 0 and 1.
    /// </summary>
    public partial class PopupPanel : UserControl
    {
        #region Fields
        bool _isLoading = false;                    // Flag to tell identify when DataContext changes
        private UIElement _lastFocusControl;        // Last control that had focus when popup visibility changes, but isn't closed
        #endregion // Fields
        #region Constructors
        public PopupPanel()
        {
            InitializeComponent();
            this.DataContextChanged += Popup_DataContextChanged;
            // Register a PropertyChanged event on IsPopupVisible
            DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty(PopupPanel.IsPopupVisibleProperty, typeof(PopupPanel));
            if (dpd != null) dpd.AddValueChanged(this, delegate { IsPopupVisible_Changed(); });
            dpd = DependencyPropertyDescriptor.FromProperty(PopupPanel.ContentProperty, typeof(PopupPanel));
            if (dpd != null) dpd.AddValueChanged(this, delegate { Content_Changed(); });
        }
        #endregion // Constructors
        #region Events
        #region Property Change Events
        // When DataContext changes
        private void Popup_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            DisableAnimationWhileLoading();
        }
        // When Content Property changes
        private void Content_Changed()
        {
            DisableAnimationWhileLoading();
        }
        // Sets an IsLoading flag so storyboard doesn't run while loading
        private void DisableAnimationWhileLoading()
        {
            _isLoading = true;
            this.Dispatcher.BeginInvoke(DispatcherPriority.Render,
                new Action(delegate() { _isLoading = false; }));
        }
        // Run storyboard when IsPopupVisible property changes to true
        private void IsPopupVisible_Changed()
        {
            bool isShown = GetIsPopupVisible(this);
            if (isShown && !_isLoading)
            {
                FrameworkElement panel = FindChild<FrameworkElement>(this, "PopupPanelContent");
                if (panel != null)
                {
                    // Run Storyboard
                    Storyboard animation = (Storyboard)panel.FindResource("ShowEditPanelStoryboard");
                    animation.Begin();
                }
            }
            // When hiding popup, clear the LastFocusControl
            if (!isShown)
            {
                _lastFocusControl = null;
            }
        }
        #endregion // Change Events
        #region Popup Events
        // When visibility is changed, set the default focus
        void PopupPanel_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            if ((bool)e.NewValue)
            {
                ContentControl popupControl = FindChild<ContentControl>(this, "PopupContentControl");
                this.Dispatcher.BeginInvoke(DispatcherPriority.Render,
                    new Action(delegate()
                    {
                        // Verify object really is visible because sometimes it's not once we switch to Render
                        if (!GetIsPopupVisible(this))
                        {
                            return;
                        }
                        if (_lastFocusControl != null && _lastFocusControl.Focusable)
                        {
                            _lastFocusControl.Focus();
                        }
                        else
                        {
                            _lastFocusControl = FindChild<UIElement>(popupControl, "PART_DefaultFocusControl") as UIElement;
                            // If we can find the part named PART_DefaultFocusControl, set focus to it
                            if (_lastFocusControl != null && _lastFocusControl.Focusable)
                            {
                                _lastFocusControl.Focus();
                            }
                            else
                            {
                                _lastFocusControl = FindFirstFocusableChild(popupControl);
                                // If no DefaultFocusControl found, try and set focus to the first focusable element found in popup
                                if (_lastFocusControl != null)
                                {
                                    _lastFocusControl.Focus();
                                }
                                else
                                {
                                    // Just give the Popup UserControl focus so it can handle keyboard input
                                    popupControl.Focus();
                                }
                            }
                        }
                    }
                    )
                );
            }
        }
        // When popup loses focus but isn't hidden, store the last element that had focus so we can put it back later
        void PopupPanel_LostFocus(object sender, RoutedEventArgs e)
        {
            DependencyObject focusScope = FocusManager.GetFocusScope(this);
            _lastFocusControl = FocusManager.GetFocusedElement(focusScope) as UIElement;
        }
        // Keyboard Events
        private void PopupPanel_PreviewKeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Escape)
            {
                PopupPanel popup = FindAncester<PopupPanel>((DependencyObject)sender);
                ICommand cmd = GetPopupEscapeKeyCommand(popup);
                if (cmd != null && cmd.CanExecute(null))
                {
                    cmd.Execute(null);
                    e.Handled = true;
                }
                else
                {
                    // By default the Escape Key closes the popup when pressed
                    var expression = this.GetBindingExpression(PopupPanel.IsPopupVisibleProperty);
                    var dataType = expression.DataItem.GetType();
                    dataType.GetProperties().Single(x => x.Name == expression.ParentBinding.Path.Path)
                        .SetValue(expression.DataItem, false, null);
                }
            }
            else if (e.Key == Key.Enter)
            {
                // Don't want to run Enter command if focus is in a TextBox with AcceptsReturn = True
                if (!(e.KeyboardDevice.FocusedElement is TextBox &&
                     (e.KeyboardDevice.FocusedElement as TextBox).AcceptsReturn == true))
                {
                    PopupPanel popup = FindAncester<PopupPanel>((DependencyObject)sender);
                    ICommand cmd = GetPopupEnterKeyCommand(popup);
                    if (cmd != null && cmd.CanExecute(null))
                    {
                        cmd.Execute(null);
                        e.Handled = true;
                    }
                }
            }
        }
        #endregion // Popup Events
        #endregion // Events
        #region Dependency Properties
        // Parent for Popup
        #region PopupParent
        public static readonly DependencyProperty PopupParentProperty =
            DependencyProperty.Register("PopupParent", typeof(FrameworkElement),
            typeof(PopupPanel), new PropertyMetadata(null, null, CoercePopupParent));
        private static object CoercePopupParent(DependencyObject obj, object value)
        {
            // If PopupParent is null, return the Window object
            return (value ?? FindAncester<Window>(obj));
        }
        public FrameworkElement PopupParent
        {
            get { return (FrameworkElement)this.GetValue(PopupParentProperty); }
            set { this.SetValue(PopupParentProperty, value); }
        }
        // Providing Get/Set methods makes them show up in the XAML designer
        public static FrameworkElement GetPopupParent(DependencyObject obj)
        {
            return (FrameworkElement)obj.GetValue(PopupParentProperty);
        }
        public static void SetPopupParent(DependencyObject obj, FrameworkElement value)
        {
            obj.SetValue(PopupParentProperty, value);
        }
        #endregion
        // Popup Visibility - If popup is shown or not
        #region IsPopupVisibleProperty
        public static readonly DependencyProperty IsPopupVisibleProperty =
            DependencyProperty.Register("IsPopupVisible", typeof(bool),
            typeof(PopupPanel), new PropertyMetadata(false, null));
        public static bool GetIsPopupVisible(DependencyObject obj)
        {
            return (bool)obj.GetValue(IsPopupVisibleProperty);
        }
        public static void SetIsPopupVisible(DependencyObject obj, bool value)
        {
            obj.SetValue(IsPopupVisibleProperty, value);
        }
        #endregion // IsPopupVisibleProperty
        // Transparency level for the background filler outside the popup
        #region BackgroundOpacityProperty
        public static readonly DependencyProperty BackgroundOpacityProperty =
            DependencyProperty.Register("BackgroundOpacity", typeof(double),
            typeof(PopupPanel), new PropertyMetadata(.5, null));
        public static double GetBackgroundOpacity(DependencyObject obj)
        {
            return (double)obj.GetValue(BackgroundOpacityProperty);
        }
        public static void SetBackgroundOpacity(DependencyObject obj, double value)
        {
            obj.SetValue(BackgroundOpacityProperty, value);
        }
        #endregion ShowBackgroundProperty
        // Command to execute when Enter key is pressed
        #region PopupEnterKeyCommandProperty
        public static readonly DependencyProperty PopupEnterKeyCommandProperty =
            DependencyProperty.RegisterAttached("PopupEnterKeyCommand", typeof(ICommand),
            typeof(PopupPanel), new PropertyMetadata(null, null));
        public static ICommand GetPopupEnterKeyCommand(DependencyObject obj)
        {
            return (ICommand)obj.GetValue(PopupEnterKeyCommandProperty);
        }
        public static void SetPopupEnterKeyCommand(DependencyObject obj, ICommand value)
        {
            obj.SetValue(PopupEnterKeyCommandProperty, value);
        }
        #endregion PopupEnterKeyCommandProperty
        // Command to execute when Enter key is pressed
        #region PopupEscapeKeyCommandProperty
        public static readonly DependencyProperty PopupEscapeKeyCommandProperty =
            DependencyProperty.RegisterAttached("PopupEscapeKeyCommand", typeof(ICommand),
            typeof(PopupPanel), new PropertyMetadata(null, null));
        public static ICommand GetPopupEscapeKeyCommand(DependencyObject obj)
        {
            return (ICommand)obj.GetValue(PopupEscapeKeyCommandProperty);
        }
        public static void SetPopupEscapeKeyCommand(DependencyObject obj, ICommand value)
        {
            obj.SetValue(PopupEscapeKeyCommandProperty, value);
        }
        #endregion PopupEscapeKeyCommandProperty
        #endregion Dependency Properties
        #region Visual Tree Helpers
        public static UIElement FindFirstFocusableChild(DependencyObject parent)
        {
            // Confirm parent is valid.
            if (parent == null) return null;
            UIElement foundChild = null;
            int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
            for (int i = 0; i < childrenCount; i++)
            {
                UIElement child = VisualTreeHelper.GetChild(parent, i) as UIElement;
                // This is returning me things like ContentControls, so for now filtering to buttons/textboxes only
                if (child != null && child.Focusable && child.IsVisible)
                {
                    foundChild = child;
                    break;
                }
                // recursively drill down the tree
                foundChild = FindFirstFocusableChild(child);
                // If the child is found, break so we do not overwrite the found child.
                if (foundChild != null) break;
            }
            return foundChild;
        }
        public static T FindAncester<T>(DependencyObject current)
        where T : DependencyObject
        {
            // Need this call to avoid returning current object if it is the same type as parent we are looking for
            current = VisualTreeHelper.GetParent(current);
            while (current != null)
            {
                if (current is T)
                {
                    return (T)current;
                }
                current = VisualTreeHelper.GetParent(current);
            };
            return null;
        }
        /// <summary>
        /// Looks for a child control within a parent by name
        /// </summary>
        public static T FindChild<T>(DependencyObject parent, string childName)
        where T : DependencyObject
        {
            // Confirm parent and childName are valid.
            if (parent == null) return null;
            T foundChild = null;
            int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
            for (int i = 0; i < childrenCount; i++)
            {
                var child = VisualTreeHelper.GetChild(parent, i);
                // If the child is not of the request child type child
                T childType = child as T;
                if (childType == null)
                {
                    // recursively drill down the tree
                    foundChild = FindChild<T>(child, childName);
                    // If the child is found, break so we do not overwrite the found child.
                    if (foundChild != null) break;
                }
                else if (!string.IsNullOrEmpty(childName))
                {
                    var frameworkElement = child as FrameworkElement;
                    // If the child's name is set for search
                    if (frameworkElement != null && frameworkElement.Name == childName)
                    {
                        // if the child's name is of the request name
                        foundChild = (T)child;
                        break;
                    }
                    else
                    {
                        // recursively drill down the tree
                        foundChild = FindChild<T>(child, childName);
                        // If the child is found, break so we do not overwrite the found child.
                        if (foundChild != null) break;
                    }
                }
                else
                {
                    // child element found.
                    foundChild = (T)child;
                    break;
                }
            }
            return foundChild;
        }
        #endregion
    }
    // Converter for Popup positioning
    public class ValueDividedByParameterConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            double n, d;
            if (double.TryParse(value.ToString(), out n)
                && double.TryParse(parameter.ToString(), out d)
                && d != 0)
            {
                return n / d;
            }
            return 0;
        }        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}