【问题标题】:Editing C# WPF PropertyGrid with Real vs Clone Properties使用真实与克隆属性编辑 C# WPF PropertyGrid
【发布时间】:2020-08-05 11:31:48
【问题描述】:

我有一个 C# WPF PropertyGrid (Denys Vuika),它是一个简单的示例,用于巩固更大实现的逻辑。我的一般方法是创建网格并使用作为显示/可编辑属性的真实属性克隆的 SelectedObject。在编辑过程中,真正的属性没有被改变——只有克隆被改变了。用户可以取消或确定编辑。取消将导致克隆重新加载真实属性,基本上丢弃所有编辑。 OK 会将真​​实属性设置为与现在编辑的克隆相同。将会有一组扩展的操作,但对于最简单的示例,只需要取消和确定即可。

我能够成功地创建、显示和编辑网格。但是,我发现使用 Real vs Clone 似乎会导致显示/存储的属性值出现问题。

示例行为:bool 属性在 Real 中为 true,然后更改为 false(理论上,更改应该只在 Clone 中发生)。 Cancel 应该将 Property 保留为 true,但实际上将其更改为 false。还有其他类似性质的奇怪行为,其中属性的状态在编辑操作后似乎不正确。在此示例中,我再次使用布尔值,但同样的行为也适用于其他数据类型。

这是 PropertyGrid 窗口的代码:

    internal class TESTPropertiesGrid : Window
    {
        private Button btnOK        = null;
        private Button btnCancel    = null;
        
        internal PropertyGrid      pgrProperties       = null;
        private  clsTestProperties pclPropertiesSource = null;
        private  clsTestProperties pclPropertiesClone  = null;
        
        // --- Constructor ---
        public TESTPropertiesGrid(clsTestProperties pSourceProperties)
        {
            pclPropertiesSource = pSourceProperties;
            
            Caption = "Test Editing Properties";
            Width   = 390;
            Height  = 320;
            
            Content = LoadXAML("TestPropertiesGrid.xaml");
            Closing += OnWindow_Closing;
        }
        
        private void OnWindow_Closing(object sender, CancelEventArgs e)
        {
            btnOK.Click         -= OnOK;
            btnCancel.Click     -= OnCancel;
            
            btnOK       = null;
            btnCancel   = null;
            
            pclPropertiesSource = null;
            pclPropertiesClone  = null;
            pgrProperties       = null;
        }
        
        private DependencyObject LoadXAML(string xamlFilename)
        {
            try
            {
                using (FileStream fs = new FileStream(xamlFilename, FileMode.Open))
                {
                    Page page = System.Windows.Markup.XamlReader.Load(fs) as Page;
                    if (page == null)
                        return null;
                    
                    DependencyObject pageContent = page.Content as DependencyObject;
                    
                    btnOK         = LogicalTreeHelper.FindLogicalNode(pageContent, "btnOK")     as Button;
                    btnCancel     = LogicalTreeHelper.FindLogicalNode(pageContent, "btnCancel") as Button;
                    pgrProperties = LogicalTreeHelper.FindLogicalNode(pageContent, "pgrDashboardProperties") as PropertyGrid;
                    
                    btnOK.Click += OnOK;
                    btnCancel.Click += OnCancel;
                    
                    ReloadProperties();
                    
                    return pageContent;
                }
            }
            catch (Exception erk)
            {
                // Output erk.ToString()
                return null;
            }
        }
        
        // --- Apply all changes to the active Properties, hide the editor ---
        private void OnOK(object sender, RoutedEventArgs e)
        {
            this.Hide();
            pclPropertiesSource = pclPropertiesClone;   // Save the Properties
            ReloadProperties();
        }
        
        // --- Ignore all changes, hide the editor ---
        private void OnCancel(object sender, RoutedEventArgs e)
        {
            this.Hide();
            ReloadProperties();
        }
        
        // --- Reload the Properties from the active Properties ---
        private void ReloadProperties()
        {
            // -- Clone the class so any changes are not pushed to the active Properties until OK'ed --
            pclPropertiesClone = null;
            pclPropertiesClone = pclPropertiesSource.Clone() as clsTestProperties;
            pgrProperties.Dispatcher.InvokeAsync(() =>
            {
                try
                {
                    pgrProperties.SelectedObject = null;
                    pgrProperties.SelectedObject = pclPropertiesClone;
                    pgrProperties.InvalidateVisual();
                }
                catch (Exception eek)
                {
                    // Output eek.ToString()
                }
            });
        }
    }

这是 Properties 类的代码。请注意,它包含顶层的属性和嵌套的属性。我发现顶层和嵌套布尔值的行为似乎不同。

    public class clsTestProperties : INotifyPropertyChanged, ICloneable
    {
        // -- Constructor to Set Defaults Properties --
        public clsTestProperties()
        {
            SelectedTestExpander = new TestExpander();
            SelectedTestExpander.NestedProperty = true;
            TestTopLevel = true;
        }
        
        // Event to flag that Property items have changed and the Property Grid should be regenerated
        public event PropertyChangedEventHandler PropertyChanged;
        
        // Event Handler to force an update to the Property Grid after changes to any Property
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            if (PropertyChanged != null)
                PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
        
        // --- Create an identical copy of this structure ---
        public object Clone()
        {
            return this.MemberwiseClone();
        }
        
        [Display(GroupName = "Test", Order = 0, Name = "NON-Nested Property")]
        public bool TestTopLevel
        { get; set; }
        
        [Display(GroupName = "Test", Order = 1, Name = "Expandable Category")]
        public TestExpander SelectedTestExpander
        { get; set; }

        [TypeConverter(typeof(ExpandableObjectConverter))]
        public class TestExpander
        {
            public TestExpander()
            {
            }

            public override string ToString()
            {
                return string.Format("");
            }

            [Display(Order = 0, Name = "Nested Property")]
            public bool NestedProperty { get; set; }
        }
    }

下面是我实例化 TESTPropertiesGrid 窗口的方式。在较大的应用程序中单击按钮后会发生这种情况,此处不包括在内。

        internal TESTPropertiesGrid wndPropertiesGrid = null;
        internal clsTestProperties  pclTestProperties = new clsTestProperties();
        
        private WindowInteropHelper wihWndProperties = null;
        
        // --- Display the Property Grid for editing ---
        private void EditProperties()
        {
            if (wndPropertiesGrid == null ||            // Never created
                wihWndProperties  == null ||            // Never created either
                wihWndProperties.Handle == IntPtr.Zero) // Window was Closed
            {
                wndPropertiesGrid = new TESTPropertiesGrid(this.pclTestProperties);
                wihWndProperties  = new WindowInteropHelper(wndPropertiesGrid);
            }
            
            wndPropertiesGrid.Dispatcher.InvokeAsync(new Action(() =>
            {
                try
                {
                    wndPropertiesGrid.Show();
                }
                catch (Exception erk)
                {
                    // Output erk.ToString()
                }
            }));
        }

这是 TESTPropertiesGrid 窗口的 XAML:

<Page   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:pg="http://schemas.denisvuyka.wordpress.com/wpfpropertygrid">
      
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="5" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="5" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="5" />
            <RowDefinition Height="*" />
            <RowDefinition Height="45" />
            <RowDefinition Height="5" />
        </Grid.RowDefinitions>
        
        <ScrollViewer Grid.Column="1" Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" VerticalScrollBarVisibility="Auto">
            <pg:PropertyGrid x:Name="pgrProperties"></pg:PropertyGrid>
        </ScrollViewer>
        
        <StackPanel Grid.Column="1" Grid.Row="2" HorizontalAlignment="Right" Orientation="Horizontal" VerticalAlignment="Bottom">
            <Button x:Name="btnOK"       Margin="5" Width="52" Content="OK"/>
            <Button x:Name="btnCancel"   Margin="5" Width="52" Content="Cancel"/>
        </StackPanel>
    </Grid>
</Page>

我没有包含实际的输出方法,因为它们是在更大的应用程序的上下文中自定义的。

希望以上所有内容都显示了我正在做的事情(并试图做)并且可以被论坛中的任何人测试。我的感觉是,我看到的行为是由我不熟悉且可能不会预料到的“幕后”行为引起的。我不明白的顶级属性和嵌套属性之间的行为似乎存在差异。可能是这样。

无论如何,我都希望更新代码,以便正确实现该概念,并且对于任何结构形式(顶层或多重嵌套)的任何/所有属性都健壮可靠。

任何建议/帮助将不胜感激!

【问题讨论】:

  • PropertyGrid 是否自行加载类属性?不包括绑定?您如何识别是否更改了任何属性?
  • 什么是“真实”对象,以及在您相当复杂的代码示例中的克隆是什么?请提供minimal 问题示例。
  • “真实”对象是 pclPropertiesSource,克隆是 pclPropertiesClone。我将真实对象传递给网格实例化方法。然后它会克隆它并将克隆用于属性的所有 UI 更改,并且仅在 OK 时更新真实对象。我将编辑该示例以减少与与本文无关的按钮/功能相关的“噪音”。 OnPropertyChanged 事件处理程序处理更改。感谢您查看此问题。
  • 编辑代码以删除各种不相关的部分。
  • 不要使用匈牙利符号! 没有人再使用它了,我的意思是没有人。

标签: c# wpf propertygrid


【解决方案1】:

进一步的研究表明,问题源于克隆操作。该代码使用 MemberwiseClone() ,这是一个浅克隆。为了能够看到嵌套属性,需要进行深度克隆。使用深度克隆使代码能够按预期工作。更多关于深度克隆的信息可以在这里找到:How to deep copy an object

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-04-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-05
    相关资源
    最近更新 更多