Редактирование C# WPF PropertyGrid с реальными и клонированными свойствами - PullRequest
0 голосов
/ 05 августа 2020

У меня есть C# WPF PropertyGrid (Denys Vuika), который представляет собой простой пример для закрепления logi c для более крупной реализации. Мой общий подход - создать сетку и использовать SelectedObject, который является клоном реальных свойств в качестве отображаемых / редактируемых свойств. Во время редактирования настоящие Свойства остаются нетронутыми - изменяется только Клон. Пользователь может отменить или подтвердить редактирование. Отмена приведет к тому, что клон перезагрузит реальные свойства, по сути, отменив все изменения. OK установит те же свойства, что и у редактируемого клонирования. Будет расширенный набор действий, но для простейшего примера все, что нужно - «Отмена» и «ОК».

Я могу успешно создавать, отображать и редактировать сетку. Однако я обнаружил, что использование Real vs Clone, похоже, вызывает проблемы со значениями отображаемых / сохраняемых свойств.

Пример поведения: свойство bool истинно в Real и заменено на false (изменение, по идее, должно произойти только в клоне). Отмена должна оставить свойство как истинное, но фактически изменяет его на ложное. Есть и другие странные поведения аналогичного характера, когда состояние свойства кажется неправильным после действия редактирования. В этом примере я использую bools снова для простоты, но то же самое происходит и с другими типами данных.

Вот код для окна 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. Обратите внимание, что он содержит свойство на верхнем уровне и вложенное свойство. Я обнаружил, что поведение верхнего уровня и вложенных bools кажется другим.

    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()
                }
            }));
        }

Вот XAML для окна TESTPropertiesGrid:

<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>

Я не включил фактические методы вывода поскольку они настраиваются в контексте более крупного приложения.

Надеюсь, все вышеперечисленное показывает, что я делаю (и пытаюсь сделать), и может быть протестировано кем угодно на форуме. У меня такое ощущение, что я наблюдаю поведение, которое вызвано «скрытыми» действиями, с которыми я не знаком и которых, возможно, не ожидаю. Кажется, есть разница в поведении между верхним уровнем и вложенными свойствами, которую я не понимаю. Это все, что может быть. ).

Мы будем благодарны за любой совет / помощь!

1 Ответ

0 голосов
/ 23 августа 2020

Дальнейшие исследования показывают, что проблема связана с операцией клонирования. Код использует MemberwiseClone (), который является мелким клоном. Чтобы увидеть вложенные свойства, нужно выполнить глубокое клонирование. Использование глубокого клона позволило коду работать должным образом. Более подробную информацию о глубоком клонировании можно найти здесь: Как глубоко скопировать объект

...