Merge pull request #207 from TorchAPI/GenericUI

UI improvements
This commit is contained in:
John Gross
2018-02-11 20:51:57 -08:00
committed by GitHub
7 changed files with 224 additions and 88 deletions

View File

@@ -118,7 +118,7 @@
</StackPanel> </StackPanel>
<Button Grid.Row="1" Content="Save Config" Margin="3" Click="Save_OnClick" /> <Button Grid.Row="1" Content="Save Config" Margin="3" Click="Save_OnClick" />
</Grid> </Grid>
<views:PropertyGrid Grid.Column="1" Margin="3" DataContext="{Binding SessionSettings}" /> <views:PropertyGrid Grid.Column="1" Margin="3" DataContext="{Binding SessionSettings}" IgnoreDisplay ="True" />
<GridSplitter Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Stretch" ShowsPreview="True" <GridSplitter Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Stretch" ShowsPreview="True"
Width="2" Background="Gray" /> Width="2" Background="Gray" />
</Grid> </Grid>

View File

@@ -263,6 +263,9 @@
<Compile Include="Views\ObjectCollectionEditor.xaml.cs"> <Compile Include="Views\ObjectCollectionEditor.xaml.cs">
<DependentUpon>ObjectCollectionEditor.xaml</DependentUpon> <DependentUpon>ObjectCollectionEditor.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="Views\ObjectEditor.xaml.cs">
<DependentUpon>ObjectEditor.xaml</DependentUpon>
</Compile>
<Compile Include="Views\PropertyGrid.xaml.cs"> <Compile Include="Views\PropertyGrid.xaml.cs">
<DependentUpon>PropertyGrid.xaml</DependentUpon> <DependentUpon>PropertyGrid.xaml</DependentUpon>
</Compile> </Compile>
@@ -291,6 +294,10 @@
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
</Page> </Page>
<Page Include="Views\ObjectEditor.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\PropertyGrid.xaml"> <Page Include="Views\PropertyGrid.xaml">
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType> <SubType>Designer</SubType>

View File

@@ -5,15 +5,24 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Torch.Views" xmlns:local="clr-namespace:Torch.Views"
mc:Ignorable="d" mc:Ignorable="d"
Height="370" Width="410" Title="Edit Collection"> Height="370" Width="400" Title="Edit Collection">
<Grid> <Grid Width="Auto" Height="Auto">
<local:PropertyGrid x:Name="PGrid" VerticalAlignment="Top" Height="300" Width="190" Margin="200,0,0,0" <Grid.ColumnDefinitions>
HorizontalAlignment="Right" /> <ColumnDefinition />
<ListBox x:Name="ElementList" Height="300" VerticalAlignment="Top" Width="190" <ColumnDefinition />
HorizontalContentAlignment="Stretch" Margin="0,0,210,0" HorizontalAlignment="Left" /> </Grid.ColumnDefinitions>
<Button x:Name="AddButton" Content="Add" HorizontalAlignment="Left" Margin="0,305,0,0" VerticalAlignment="Top" <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" /> Width="90" />
<Button x:Name="RemoveButton" Content="Remove" HorizontalAlignment="Left" Margin="100,305,0,0" <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" /> VerticalAlignment="Top" Width="90" />
</Grid> </Grid>

View File

@@ -0,0 +1,11 @@
<Window x:Class="Torch.Views.ObjectEditor"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Torch.Views"
mc:Ignorable="d"
Height="400" Width="400" Title="Edit Object">
<local:PropertyGrid x:Name="PGrid" />
</Window>

View File

@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
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;
namespace Torch.Views
{
/// <summary>
/// Interaction logic for ObjectEditor.xaml
/// </summary>
public partial class ObjectEditor : Window
{
public ObjectEditor()
{
InitializeComponent();
}
public void Edit(object o, string title = "Edit Object")
{
PGrid.DataContext = o;
Title = title;
WindowStartupLocation = WindowStartupLocation.CenterOwner;
ShowDialog();
}
}
}

View File

@@ -9,6 +9,7 @@
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition/> <RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid Grid.Row="0"> <Grid Grid.Row="0">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
@@ -19,5 +20,6 @@
<TextBox Grid.Column="1" Margin="3" TextChanged="UpdateFilter"/> <TextBox Grid.Column="1" Margin="3" TextChanged="UpdateFilter"/>
</Grid> </Grid>
<ScrollViewer Grid.Row="1" x:Name="ScrollViewer"/> <ScrollViewer Grid.Row="1" x:Name="ScrollViewer"/>
<TextBlock x:Name="TbDescription" Grid.Row="2" MinHeight="18" Background="#FFBBB9B9"/>
</Grid> </Grid>
</UserControl> </UserControl>

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@@ -29,6 +30,14 @@ namespace Torch.Views
{ {
private Dictionary<Type, Grid> _viewCache = new Dictionary<Type, Grid>(); private Dictionary<Type, Grid> _viewCache = new Dictionary<Type, Grid>();
public static readonly DependencyProperty IgnoreDisplayProperty = DependencyProperty.Register("IgnoreDisplay", typeof(bool), typeof(PropertyGrid));
public bool IgnoreDisplay
{
get => (bool)base.GetValue(IgnoreDisplayProperty);
set => base.SetValue(IgnoreDisplayProperty, value);
}
public PropertyGrid() public PropertyGrid()
{ {
InitializeComponent(); InitializeComponent();
@@ -59,114 +68,171 @@ namespace Torch.Views
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(2, GridUnitType.Star) }); grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(2, GridUnitType.Star) });
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }); grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
var curRow = 0; var categories = new Dictionary<string, List<PropertyInfo>>();
foreach (var property in properties.OrderBy(x => x.Name)) var descriptors = new Dictionary<PropertyInfo, DisplayAttribute>(properties.Length);
foreach (var property in properties)
{ {
if (property.GetGetMethod() == null) var a = property.GetCustomAttribute<DisplayAttribute>();
continue; if (IgnoreDisplay)
a = null;
descriptors[property] = a;
string category = a?.GroupName ?? "Misc";
if (!categories.TryGetValue(category, out List<PropertyInfo> l))
{
l = new List<PropertyInfo>();
categories[category] = l;
}
l.Add(property);
}
var curRow = 0;
foreach (var c in categories.OrderBy(x => x.Key))
{
grid.RowDefinitions.Add(new RowDefinition()); grid.RowDefinitions.Add(new RowDefinition());
var cl = new TextBlock
{
Text = c.Key,
VerticalAlignment = VerticalAlignment.Center
};
cl.SetValue(Grid.ColumnProperty, 0);
cl.SetValue(Grid.ColumnSpanProperty, 2);
cl.SetValue(Grid.RowProperty, curRow);
cl.Margin = new Thickness(3);
cl.FontWeight = FontWeights.Bold;
grid.Children.Add(cl);
curRow++;
var displayName = property.GetCustomAttribute<DisplayAttribute>()?.Name; c.Value.Sort((a,b)=> string.Compare((descriptors[a]?.Name ?? a.Name), descriptors[b]?.Name ?? b.Name, StringComparison.Ordinal));
var propertyType = property.PropertyType;
var text = new TextBlock foreach (var property in c.Value)
{ {
Text = property.Name, if (property.GetGetMethod() == null)
ToolTip = displayName, continue;
VerticalAlignment = VerticalAlignment.Center
};
text.SetValue(Grid.ColumnProperty, 0);
text.SetValue(Grid.RowProperty, curRow);
text.Margin = new Thickness(3);
grid.Children.Add(text);
FrameworkElement valueControl; grid.RowDefinitions.Add(new RowDefinition());
if (property.GetSetMethod() == null)
{ var descriptor = descriptors[property];
valueControl = new TextBlock(); var displayName = descriptor?.Name;
var binding = new Binding(property.Name) var propertyType = property.PropertyType;
var text = new TextBlock
{
Text = displayName ?? property.Name,
ToolTip = displayName,
VerticalAlignment = VerticalAlignment.Center
};
text.SetValue(Grid.ColumnProperty, 0);
text.SetValue(Grid.RowProperty, curRow);
text.Margin = new Thickness(3);
text.Tag = $"{text.Text}: {descriptor?.Description}";
text.IsMouseDirectlyOverChanged += Text_IsMouseDirectlyOverChanged;
grid.Children.Add(text);
FrameworkElement valueControl;
if (property.GetSetMethod() == null)
{ {
Mode = BindingMode.OneWay valueControl = new TextBlock();
}; var binding = new Binding(property.Name)
valueControl.SetBinding(TextBlock.TextProperty, binding); {
} Mode = BindingMode.OneWay
else if (propertyType == typeof(bool) || propertyType == typeof(bool?)) };
{ valueControl.SetBinding(TextBlock.TextProperty, binding);
valueControl = new CheckBox(); }
valueControl.SetBinding(CheckBox.IsCheckedProperty, property.Name); else if (propertyType == typeof(bool) || propertyType == typeof(bool?))
}
else if (propertyType.IsEnum)
{
valueControl = new ComboBox
{ {
ItemsSource = Enum.GetValues(property.PropertyType) valueControl = new CheckBox();
}; valueControl.SetBinding(CheckBox.IsCheckedProperty, property.Name);
valueControl.SetBinding(ComboBox.SelectedItemProperty, property.Name); }
} else if (propertyType.IsEnum)
else if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(Dictionary<,>))
{
var button = new Button
{ {
Content = "Edit Collection" valueControl = new ComboBox
}; {
button.SetBinding(Button.DataContextProperty, property.Name); ItemsSource = Enum.GetValues(property.PropertyType)
button.Click += (sender, args) => EditDictionary(((Button)sender).DataContext); };
valueControl.SetBinding(ComboBox.SelectedItemProperty, property.Name);
valueControl = button; }
} else if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(Dictionary<,>))
else if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(SerializableDictionary<,>))
{
var button = new Button
{ {
Content = "Edit Collection"
};
button.SetBinding(Button.DataContextProperty, $"{property.Name}.Dictionary");
button.Click += (sender, args) => EditDictionary(((Button)sender).DataContext);
valueControl = button;
}
else if (propertyType.IsGenericType && typeof(ICollection).IsAssignableFrom(propertyType.GetGenericTypeDefinition()))
{
var button = new Button var button = new Button
{ {
Content = "Edit Collection" Content = "Edit Collection"
}; };
button.SetBinding(Button.DataContextProperty, $"{property.Name}"); button.SetBinding(Button.DataContextProperty, property.Name);
button.Click += (sender, args) => EditDictionary(((Button)sender).DataContext);
var gt = propertyType.GetGenericArguments()[0]; valueControl = button;
}
//TODO: Is this the best option? Probably not else if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(SerializableDictionary<,>))
if (gt.IsPrimitive || gt == typeof(string))
{ {
button.Click += (sender, args) => EditPrimitiveCollection(((Button)sender).DataContext); var button = new Button
{
Content = "Edit Collection"
};
button.SetBinding(Button.DataContextProperty, $"{property.Name}.Dictionary");
button.Click += (sender, args) => EditDictionary(((Button)sender).DataContext);
valueControl = button;
}
else if (propertyType.IsGenericType && typeof(ICollection).IsAssignableFrom(propertyType.GetGenericTypeDefinition()))
{
var button = new Button
{
Content = "Edit Collection"
};
button.SetBinding(Button.DataContextProperty, property.Name);
var gt = propertyType.GetGenericArguments()[0];
//TODO: Is this the best option? Probably not
if (gt.IsPrimitive || gt == typeof(string))
{
button.Click += (sender, args) => EditPrimitiveCollection(((Button)sender).DataContext);
}
else
{
button.Click += (sender, args) => EditObjectCollection(((Button)sender).DataContext);
}
valueControl = button;
}
else if (propertyType.IsPrimitive || propertyType == typeof(string))
{
valueControl = new TextBox();
valueControl.SetBinding(TextBox.TextProperty, property.Name);
} }
else else
{ {
button.Click += (sender, args) => EditObjectCollection(((Button)sender).DataContext); var button = new Button
{
Content = "Edit Object"
};
button.SetBinding(Button.DataContextProperty, property.Name);
button.Click += (sender, args) => EditObject(((Button)sender).DataContext);
valueControl = button;
} }
valueControl = button; valueControl.Margin = new Thickness(3);
} valueControl.VerticalAlignment = VerticalAlignment.Center;
else valueControl.SetValue(Grid.ColumnProperty, 1);
{ valueControl.SetValue(Grid.RowProperty, curRow);
valueControl = new TextBox(); valueControl.IsMouseDirectlyOverChanged += Text_IsMouseDirectlyOverChanged;
valueControl.SetBinding(TextBox.TextProperty, property.Name); grid.Children.Add(valueControl);
}
valueControl.Margin = new Thickness(3); curRow++;
valueControl.VerticalAlignment = VerticalAlignment.Center; }
valueControl.SetValue(Grid.ColumnProperty, 1);
valueControl.SetValue(Grid.RowProperty, curRow);
grid.Children.Add(valueControl);
curRow++;
} }
_viewCache.Add(t, grid); _viewCache.Add(t, grid);
return grid; return grid;
} }
private void Text_IsMouseDirectlyOverChanged(object sender, DependencyPropertyChangedEventArgs e)
{
TbDescription.Text = (sender as FrameworkElement)?.Tag?.ToString() ?? string.Empty;
}
private void EditDictionary(object dict) private void EditDictionary(object dict)
{ {
var dic = (IDictionary)dict; var dic = (IDictionary)dict;
@@ -185,6 +251,11 @@ namespace Torch.Views
new ObjectCollectionEditor().Edit(c, title); new ObjectCollectionEditor().Edit(c, title);
} }
private void EditObject(object o, string title = "Edit Object")
{
new ObjectEditor().Edit(o, title);
}
private void UpdateFilter(object sender, TextChangedEventArgs e) private void UpdateFilter(object sender, TextChangedEventArgs e)
{ {
var filterText = ((TextBox)sender).Text; var filterText = ((TextBox)sender).Text;