Refactor ObjectCollectionEditor so it's embeddable in a propertygrid.

Make the listbox actually update when items are added/removed.
This commit is contained in:
Brant Martin
2018-07-03 14:47:19 -04:00
parent 736176ce27
commit 7bad6149b5
5 changed files with 167 additions and 93 deletions

View File

@@ -273,6 +273,9 @@
<DependentUpon>DictionaryEditor.xaml</DependentUpon> <DependentUpon>DictionaryEditor.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="Views\DisplayAttribute.cs" /> <Compile Include="Views\DisplayAttribute.cs" />
<Compile Include="Views\EmbeddedCollectionEditor.xaml.cs">
<DependentUpon>EmbeddedCollectionEditor.xaml</DependentUpon>
</Compile>
<Compile Include="Views\ObjectCollectionEditor.xaml.cs"> <Compile Include="Views\ObjectCollectionEditor.xaml.cs">
<DependentUpon>ObjectCollectionEditor.xaml</DependentUpon> <DependentUpon>ObjectCollectionEditor.xaml</DependentUpon>
</Compile> </Compile>
@@ -303,6 +306,10 @@
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType> <SubType>Designer</SubType>
</Page> </Page>
<Page Include="Views\EmbeddedCollectionEditor.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\ObjectCollectionEditor.xaml"> <Page Include="Views\ObjectCollectionEditor.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>

View File

@@ -0,0 +1,32 @@
<UserControl x:Class="Torch.Views.EmbeddedCollectionEditor"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Torch.Views"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid Width="Auto" Height="Auto">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="25" />
</Grid.RowDefinitions>
<ListBox Grid.Row="0" Grid.Column="0" x:Name="ElementList"
HorizontalContentAlignment="Stretch" Margin="0" VerticalContentAlignment="Stretch" />
<GridSplitter Grid.Column="1" Grid.Row="0" Width="2" HorizontalAlignment="Left" VerticalAlignment="Stretch"
Background="Gray" ShowsPreview="True" VerticalContentAlignment="Stretch" />
<local:PropertyGrid Grid.Row="0" Grid.Column="1" x:Name="PGrid" Margin="4,0,0,0" />
<Button Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" x:Name="AddButton" Content="Add"
HorizontalAlignment="Left" Margin="0" VerticalAlignment="Top"
Width="90" />
<Button Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" x:Name="RemoveButton" Content="Remove"
HorizontalAlignment="Left" Margin="100,0,0,0"
VerticalAlignment="Top" Width="90" />
</Grid>
</UserControl>

View File

@@ -0,0 +1,126 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using NLog;
using NLog.Fluent;
namespace Torch.Views
{
/// <summary>
/// Interaction logic for EmbeddedCollectionEditor.xaml
/// </summary>
public partial class EmbeddedCollectionEditor : UserControl
{
public EmbeddedCollectionEditor()
{
InitializeComponent();
DataContextChanged += OnDataContextChanged;
}
private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var c = dependencyPropertyChangedEventArgs.NewValue as ICollection;
//var c = DataContext as ICollection;
if (c != null)
Edit(c);
}
private static readonly Dictionary<Type, MethodInfo> MethodCache = new Dictionary<Type, MethodInfo>();
private static readonly MethodInfo EditMethod;
static EmbeddedCollectionEditor()
{
var m = typeof(EmbeddedCollectionEditor).GetMethods();
EditMethod = m.First(mt => mt.Name == "Edit" && mt.GetGenericArguments().Length == 1);
}
public void Edit(ICollection collection)
{
if (collection == null)
{
MessageBox.Show("Cannot load null collection.", "Edit Error");
return;
}
var gt = collection.GetType().GenericTypeArguments[0];
//substitute for 'where T : new()'
if (gt.GetConstructor(Type.EmptyTypes) == null)
{
MessageBox.Show("Unsupported collection type. Type must have paramaterless ctor.", "Edit Error");
return;
}
if (!MethodCache.TryGetValue(gt, out MethodInfo gm))
{
gm = EditMethod.MakeGenericMethod(gt);
MethodCache.Add(gt, gm);
}
gm.Invoke(this, new object[] {collection});
}
public void Edit<T>(ICollection<T> collection) where T : new()
{
var oc = collection as ObservableCollection<T> ?? new ObservableCollection<T>(collection);
AddButton.Click += (sender, args) =>
{
var t = new T();
oc.Add(t);
ElementList.SelectedItem = t;
};
RemoveButton.Click += RemoveButton_OnClick<T>;
ElementList.SelectionChanged += ElementsList_OnSelected;
ElementList.ItemsSource = oc;
oc.CollectionChanged += (sender, args) => RefreshList();
if (!(collection is ObservableCollection<T>))
{
collection.Clear();
foreach (var o in oc)
collection.Add(o);
}
}
private void RemoveButton_OnClick<T>(object sender, RoutedEventArgs e)
{
//this is kinda shitty, but item count is normally small, and it prevents CollectionModifiedExceptions
var l = (ObservableCollection<T>)ElementList.ItemsSource;
var r = new List<T>(ElementList.SelectedItems.Cast<T>());
foreach (var item in r)
l.Remove(item);
if (l.Any())
ElementList.SelectedIndex = 0;
}
private void ElementsList_OnSelected(object sender, RoutedEventArgs e)
{
var item = (sender as ListBox)?.SelectedItem;
PGrid.DataContext = item;
}
private void RefreshList()
{
ElementList.Items.Refresh();
}
}
}

View File

@@ -6,24 +6,5 @@
xmlns:local="clr-namespace:Torch.Views" xmlns:local="clr-namespace:Torch.Views"
mc:Ignorable="d" mc:Ignorable="d"
Height="370" Width="400" Title="Edit Collection"> Height="370" Width="400" Title="Edit Collection">
<Grid Width="Auto" Height="Auto"> <local:EmbeddedCollectionEditor x:Name="Editor"/>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="25"/>
</Grid.RowDefinitions>
<ListBox Grid.Row="0" Grid.Column="0" x:Name="ElementList"
HorizontalContentAlignment="Stretch" Margin="0" VerticalContentAlignment="Stretch" />
<GridSplitter Grid.Column="1" Grid.Row="0" Width="2" HorizontalAlignment="Left" VerticalAlignment="Stretch" Background="Gray" ShowsPreview="True" VerticalContentAlignment="Stretch"/>
<local:PropertyGrid Grid.Row="0" Grid.Column="1" x:Name="PGrid" Margin="4,0,0,0" />
<Button Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" x:Name="AddButton" Content="Add" HorizontalAlignment="Left" Margin="0" VerticalAlignment="Top"
Width="90" />
<Button Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" x:Name="RemoveButton" Content="Remove" HorizontalAlignment="Left" Margin="100,0,0,0"
VerticalAlignment="Top" Width="90" />
</Grid>
</Window> </Window>

View File

@@ -25,90 +25,18 @@ namespace Torch.Views
/// </summary> /// </summary>
public partial class ObjectCollectionEditor : Window public partial class ObjectCollectionEditor : Window
{ {
private static readonly Dictionary<Type, MethodInfo> MethodCache = new Dictionary<Type, MethodInfo>();
private static readonly MethodInfo EditMethod;
public ObjectCollectionEditor() public ObjectCollectionEditor()
{ {
InitializeComponent(); InitializeComponent();
} }
static ObjectCollectionEditor()
{
var m = typeof(ObjectCollectionEditor).GetMethods();
EditMethod = m.First(mt => mt.Name == "Edit" && mt.GetGenericArguments().Length == 1);
}
public void Edit(ICollection collection, string title) public void Edit(ICollection collection, string title)
{ {
if (collection == null) Editor.Edit(collection);
{
MessageBox.Show("Cannot load null collection.", "Edit Error");
return;
}
var gt = collection.GetType().GenericTypeArguments[0];
//substitute for 'where T : new()'
if (gt.GetConstructor(Type.EmptyTypes) == null)
{
MessageBox.Show("Unsupported collection type. Type must have paramaterless ctor.", "Edit Error");
return;
}
if (!MethodCache.TryGetValue(gt, out MethodInfo gm))
{
gm = EditMethod.MakeGenericMethod(gt);
MethodCache.Add(gt, gm);
}
gm.Invoke(this, new object[] {collection, title});
}
public void Edit<T>(ICollection<T> collection, string title) where T : new()
{
var oc = collection as ObservableCollection<T> ?? new ObservableCollection<T>(collection);
AddButton.Click += (sender, args) =>
{
var t = new T();
oc.Add(t);
ElementList.SelectedItem = t;
};
RemoveButton.Click += RemoveButton_OnClick<T>;
ElementList.SelectionChanged += ElementsList_OnSelected;
ElementList.ItemsSource = oc;
Title = title; Title = title;
WindowStartupLocation = WindowStartupLocation.CenterOwner; WindowStartupLocation = WindowStartupLocation.CenterOwner;
ShowDialog(); ShowDialog();
if (!(collection is ObservableCollection<T>))
{
collection.Clear();
foreach (var o in oc)
collection.Add(o);
}
}
private void RemoveButton_OnClick<T>(object sender, RoutedEventArgs e)
{
//this is kinda shitty, but item count is normally small, and it prevents CollectionModifiedExceptions
var l = (ObservableCollection<T>)ElementList.ItemsSource;
var r = new List<T>(ElementList.SelectedItems.Cast<T>());
foreach (var item in r)
l.Remove(item);
if (l.Any())
ElementList.SelectedIndex = 0;
}
private void ElementsList_OnSelected(object sender, RoutedEventArgs e)
{
var item = (sender as ListBox)?.SelectedItem;
PGrid.DataContext = item;
} }
} }
} }