【发布时间】: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