GUI: Add plugin option validation + many bug fixes to dialog

This commit is contained in:
Katy Coe
2020-12-23 22:31:27 +01:00
parent 482f08018f
commit e1adf20951
3 changed files with 231 additions and 135 deletions

View File

@@ -1,143 +1,52 @@
<Window x:Class="Il2CppInspector.GUI.PluginConfigurationDialog" <Window x:Class="Il2CppInspectorGUI.PluginConfigurationDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Il2CppInspector.GUI" xmlns:local="clr-namespace:Il2CppInspectorGUI"
xmlns:pluginapi="clr-namespace:Il2CppInspector.PluginAPI.V100;assembly=Il2CppInspector.Common" xmlns:local2="clr-namespace:Il2CppInspector.GUI"
mc:Ignorable="d" mc:Ignorable="d"
Title="{Binding Path=Plugin.Name, StringFormat=Configuration for {0}}" Height="400" Width="800" Title="{Binding Path=Plugin.Name, StringFormat=Configuration for {0}}" Height="400" Width="800"
ResizeMode="NoResize" ResizeMode="NoResize"
WindowStartupLocation="CenterOwner" WindowStartupLocation="CenterOwner"
SizeToContent="Height" MaxHeight="800"> SizeToContent="Height" MaxHeight="800"
Closing="Window_Closing">
<Window.Resources> <Window.Resources>
<local:OptionTemplateSelector x:Key="OptionTemplateSelector"/> <local:OptionTemplateSelector x:Key="OptionTemplateSelector"/>
<local:HexStringValueConverter x:Key="HexStringValueConverter" /> <local2:HexStringValueConverter x:Key="HexStringValueConverter" />
<local:EqualityConverter x:Key="EqualityVisibilityConverter" TrueValue="{x:Static Visibility.Visible}" FalseValue="{x:Static Visibility.Collapsed}" /> <local2:EqualityConverter x:Key="EqualityVisibilityConverter" TrueValue="{x:Static Visibility.Visible}" FalseValue="{x:Static Visibility.Collapsed}" />
<BooleanToVisibilityConverter x:Key="VisibleIfTrueConverter" /> <BooleanToVisibilityConverter x:Key="VisibleIfTrueConverter" />
<!-- Option layouts --> <!-- Validation error style -->
<DataTemplate x:Key="TextTemplate"> <Style x:Key="ValidationStyle" TargetType="TextBlock">
<DockPanel> <Setter Property="Text" Value="{Binding ElementName=valueControl, Path=(Validation.Errors)[0].ErrorContent}"/>
<TextBlock DockPanel.Dock="Left" Width="350" VerticalAlignment="Center"> <Setter Property="Foreground" Value="Red" />
<TextBlock Text="{Binding Path=Description}" TextWrapping="Wrap"></TextBlock> </Style>
<TextBlock Visibility="{Binding Required, Converter={StaticResource VisibleIfTrueConverter}}" Text="*" Foreground="Red"/>
</TextBlock>
<TextBox DockPanel.Dock="Right" VerticalAlignment="Center" Padding="2" Margin="0,4,4,4" Text="{Binding Path=Value, UpdateSourceTrigger=PropertyChanged}"></TextBox>
</DockPanel>
</DataTemplate>
<DataTemplate x:Key="FilePathTemplate"> <DataTemplate x:Key="ValidationErrorTemplate">
<DockPanel>
<Button Name="btnFilePathSelector" DockPanel.Dock="Right" Width="70" Margin="4" Click="btnFilePathSelector_Click">Browse</Button>
<TextBlock DockPanel.Dock="Left" Width="3500" VerticalAlignment="Center">
<TextBlock Text="{Binding Description}" TextWrapping="Wrap"></TextBlock>
<TextBlock Visibility="{Binding Required, Converter={StaticResource VisibleIfTrueConverter}}" Text="*" Foreground="Red"/>
</TextBlock>
<TextBlock Name="txtFilePathSelector" DockPanel.Dock="Right" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="3" ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=Text}" Text="{Binding Value}"/>
</DockPanel>
</DataTemplate>
<DataTemplate x:Key="NumberTemplate">
<StackPanel> <StackPanel>
<!-- Decimal number --> <StackPanel.Style>
<DockPanel> <Style TargetType="{x:Type StackPanel}">
<DockPanel.Visibility> <Style.Triggers>
<MultiBinding Converter="{StaticResource EqualityVisibilityConverter}"> <DataTrigger Binding="{Binding ElementName=errorText, Path=Text}" Value="">
<Binding Path="Style" /> <Setter Property="Visibility" Value="Collapsed"/>
<Binding Source="{x:Static pluginapi:PluginOptionNumberStyle.Decimal}" Mode="OneWay" /> </DataTrigger>
</MultiBinding> </Style.Triggers>
</DockPanel.Visibility> </Style>
</StackPanel.Style>
<TextBlock DockPanel.Dock="Left" Width="350" VerticalAlignment="Center"> <TextBlock Name="errorText" Style="{StaticResource ValidationStyle}" />
<TextBlock Text="{Binding Description}" TextWrapping="Wrap"></TextBlock>
<TextBlock Visibility="{Binding Required, Converter={StaticResource VisibleIfTrueConverter}}" Text="*" Foreground="Red"/>
</TextBlock>
<TextBox Name="txtDecimalString" DockPanel.Dock="Right" VerticalAlignment="Center" Padding="2" Margin="0,4,4,4" Text="{Binding Path=Value, UpdateSourceTrigger=PropertyChanged}"></TextBox>
</DockPanel>
<!-- Hex number -->
<DockPanel>
<DockPanel.Visibility>
<MultiBinding Converter="{StaticResource EqualityVisibilityConverter}">
<Binding Path="Style" />
<Binding Source="{x:Static pluginapi:PluginOptionNumberStyle.Hex}" Mode="OneWay" />
</MultiBinding>
</DockPanel.Visibility>
<TextBlock DockPanel.Dock="Left" Width="350" VerticalAlignment="Center">
<TextBlock Text="{Binding Description}" TextWrapping="Wrap"></TextBlock>
<TextBlock Visibility="{Binding Required, Converter={StaticResource VisibleIfTrueConverter}}" Text="*" Foreground="Red"/>
</TextBlock>
<DockPanel HorizontalAlignment="Stretch">
<Label Margin="0,3,3,5">0x</Label>
<TextBox Name="txtHexString" Padding="2" Margin="0,6,4,6" Text="{Binding Value, Converter={StaticResource HexStringValueConverter}, UpdateSourceTrigger=PropertyChanged}" PreviewTextInput="txtHexString_PreviewTextInput"/>
</DockPanel>
</DockPanel>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="BooleanTemplate">
<DockPanel Margin="350,0,0,0">
<TextBlock DockPanel.Dock="Left" VerticalAlignment="Top" Margin="0,10,2,6"
Visibility="{Binding Required, Converter={StaticResource VisibleIfTrueConverter}}" Text="*" Foreground="Red"/>
<CheckBox DockPanel.Dock="Right" VerticalAlignment="Center" Margin="0,10,2,6" IsChecked="{Binding Value}">
<TextBlock VerticalAlignment="Center" TextWrapping="Wrap" Text="{Binding Description}" />
</CheckBox>
</DockPanel>
</DataTemplate>
<DataTemplate x:Key="ChoiceTemplate">
<StackPanel>
<!-- Drop-down box version -->
<DockPanel>
<DockPanel.Visibility>
<MultiBinding Converter="{StaticResource EqualityVisibilityConverter}">
<Binding Path="Style" />
<Binding Source="{x:Static pluginapi:PluginOptionChoiceStyle.Dropdown}" Mode="OneWay" />
</MultiBinding>
</DockPanel.Visibility>
<TextBlock DockPanel.Dock="Left" Width="350" VerticalAlignment="Center">
<TextBlock Text="{Binding Description}" TextWrapping="Wrap"></TextBlock>
<TextBlock Visibility="{Binding Required, Converter={StaticResource VisibleIfTrueConverter}}" Text="*" Foreground="Red"/>
</TextBlock>
<ComboBox DockPanel.Dock="Right" Margin="4,8,4,8" ItemsSource="{Binding Choices}" DisplayMemberPath="Value" SelectedValuePath="Key" SelectedValue="{Binding Value}"></ComboBox>
</DockPanel>
<!-- Radio buttons version -->
<DockPanel>
<DockPanel.Visibility>
<MultiBinding Converter="{StaticResource EqualityVisibilityConverter}">
<Binding Path="Style" />
<Binding Source="{x:Static pluginapi:PluginOptionChoiceStyle.List}" Mode="OneWay" />
</MultiBinding>
</DockPanel.Visibility>
<GroupBox Header="{Binding Description}" Margin="5" Padding="5">
<ListBox ItemsSource="{Binding Choices}" SelectedItem="{Binding Value}" BorderBrush="Transparent">
<ListBox.ItemTemplate>
<DataTemplate>
<RadioButton GroupName="{Binding Header, RelativeSource={RelativeSource AncestorType=GroupBox}}"
Content="{Binding}"
IsChecked="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListBoxItem}}"
Focusable="False"
IsHitTestVisible="False"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</GroupBox>
</DockPanel>
</StackPanel> </StackPanel>
</DataTemplate> </DataTemplate>
<!-- Configure ListBox to display configuration controls nicely --> <!-- Configure ListBox to display configuration controls nicely -->
<Style TargetType="{x:Type ListBoxItem}"> <Style x:Key="OptionItemStyle" TargetType="{x:Type ListBoxItem}">
<Setter Property="Background" Value="Transparent"/> <Setter Property="Background" Value="Transparent"/>
<!-- Force ListBox to width of window --> <!-- Force ListBox to width of window -->
<Setter Property="HorizontalContentAlignment" Value="Stretch"/> <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/> <Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
<Setter Property="Padding" Value="2,0,0,0"/> <Setter Property="Padding" Value="2,6,0,6"/>
<Setter Property="Template"> <Setter Property="Template">
<Setter.Value> <Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}"> <ControlTemplate TargetType="{x:Type ListBoxItem}">
@@ -153,8 +62,115 @@
</Setter.Value> </Setter.Value>
</Setter> </Setter>
</Style> </Style>
<!-- Option label template -->
<DataTemplate x:Key="OptionLabelTemplate">
<TextBlock DockPanel.Dock="Left" Width="350" VerticalAlignment="Top">
<TextBlock Text="{Binding Path=Description}" TextWrapping="Wrap" Margin="0,4,0,0"></TextBlock>
<TextBlock Visibility="{Binding Required, Converter={StaticResource VisibleIfTrueConverter}}" Text="*" Foreground="Red"/>
</TextBlock>
</DataTemplate>
<!-- Option layouts -->
<!-- Free text -->
<DataTemplate x:Key="TextTemplate">
<DockPanel>
<ContentPresenter ContentTemplate="{StaticResource OptionLabelTemplate}" />
<StackPanel DockPanel.Dock="Right" Margin="4,0,4,0">
<TextBox Name="valueControl" VerticalAlignment="Top" Padding="2" Text="{Binding Path=Value, UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=True}" />
<ContentPresenter ContentTemplate="{StaticResource ValidationErrorTemplate}" />
</StackPanel>
</DockPanel>
</DataTemplate>
<!-- File path -->
<DataTemplate x:Key="FilePathTemplate">
<DockPanel>
<Button Name="btnFilePathSelector" DockPanel.Dock="Right" Width="70" Height="25" VerticalAlignment="Top" Margin="4,0,4,0" Click="btnFilePathSelector_Click">Browse</Button>
<ContentPresenter ContentTemplate="{StaticResource OptionLabelTemplate}" />
<StackPanel DockPanel.Dock="Right" Margin="4,0,4,0">
<TextBox Name="valueControl" VerticalAlignment="Top" HorizontalAlignment="Stretch" TextAlignment="Right" Padding="2" Margin="0,0,4,0" IsReadOnly="True" BorderBrush="Transparent" ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=Text}" Text="{Binding Value, UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=True}" />
<ContentPresenter ContentTemplate="{StaticResource ValidationErrorTemplate}" />
</StackPanel>
</DockPanel>
</DataTemplate>
<!-- Decimal number -->
<DataTemplate x:Key="NumberDecimalTemplate">
<DockPanel>
<ContentPresenter ContentTemplate="{StaticResource OptionLabelTemplate}" />
<StackPanel DockPanel.Dock="Right" Margin="4,0,4,0">
<TextBox Name="valueControl" VerticalAlignment="Center" Padding="2" Text="{Binding Value, UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=True}"/>
<ContentPresenter ContentTemplate="{StaticResource ValidationErrorTemplate}" />
</StackPanel>
</DockPanel>
</DataTemplate>
<!-- Hex number -->
<DataTemplate x:Key="NumberHexTemplate">
<DockPanel>
<ContentPresenter ContentTemplate="{StaticResource OptionLabelTemplate}" />
<StackPanel DockPanel.Dock="Right" Margin="4,0,4,0">
<DockPanel HorizontalAlignment="Stretch">
<Label Padding="0" Margin="0,2,4,0">0x</Label>
<TextBox Name="valueControl" DockPanel.Dock="Right" Padding="2" Text="{Binding Value, Converter={StaticResource HexStringValueConverter}, UpdateSourceTrigger=PropertyChanged}" PreviewTextInput="txtHexString_PreviewTextInput"/>
</DockPanel>
<ContentPresenter ContentTemplate="{StaticResource ValidationErrorTemplate}" />
</StackPanel>
</DockPanel>
</DataTemplate>
<!-- Boolean tickbox (no validation required) -->
<DataTemplate x:Key="BooleanTemplate">
<DockPanel Margin="350,0,0,0">
<TextBlock DockPanel.Dock="Left" VerticalAlignment="Top" Margin="0,4,2,4"
Visibility="{Binding Required, Converter={StaticResource VisibleIfTrueConverter}}" Text="*" Foreground="Red"/>
<CheckBox Name="valueControl" DockPanel.Dock="Right" VerticalAlignment="Center" Margin="0,4,2,4" IsChecked="{Binding Value}">
<TextBlock VerticalAlignment="Center" TextWrapping="Wrap" Text="{Binding Description}" />
</CheckBox>
</DockPanel>
</DataTemplate>
<!-- Drop-down choices -->
<DataTemplate x:Key="ChoiceDropdownTemplate">
<DockPanel>
<ContentPresenter ContentTemplate="{StaticResource OptionLabelTemplate}" />
<StackPanel DockPanel.Dock="Right" Margin="4,0,4,0">
<ComboBox Name="valueControl" ItemsSource="{Binding Choices}" DisplayMemberPath="Value" SelectedValuePath="Key" SelectedValue="{Binding Value}"/>
<ContentPresenter ContentTemplate="{StaticResource ValidationErrorTemplate}" />
</StackPanel>
</DockPanel>
</DataTemplate>
<!-- Radio button choices -->
<DataTemplate x:Key="ChoiceListTemplate">
<StackPanel>
<GroupBox Header="{Binding Description}" Margin="5" Padding="5">
<ListBox Name="valueControl" ItemsSource="{Binding Choices}" SelectedValuePath="Key" SelectedValue="{Binding Value}" BorderBrush="Transparent">
<ListBox.ItemTemplate>
<DataTemplate>
<RadioButton GroupName="{Binding Header, RelativeSource={RelativeSource AncestorType=GroupBox}}"
Content="{Binding Value}"
IsChecked="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListBoxItem}}"
Focusable="False"
IsHitTestVisible="False"/>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource OptionItemStyle}">
<Setter Property="Padding" Value="0" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</GroupBox>
<ContentPresenter ContentTemplate="{StaticResource ValidationErrorTemplate}" />
</StackPanel>
</DataTemplate>
</Window.Resources> </Window.Resources>
<!-- Main dialog window -->
<Grid Margin="10"> <Grid Margin="10">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="50" /> <RowDefinition Height="50" />
@@ -167,13 +183,15 @@
Content="{Binding Plugin.Name}" ContentStringFormat="Configuration for {0}" Padding="8,0,0,0"/> Content="{Binding Plugin.Name}" ContentStringFormat="Configuration for {0}" Padding="8,0,0,0"/>
<!-- Options editor --> <!-- Options editor -->
<ListBox Grid.Row="1" Name="lstOptions" ItemsSource="{Binding Plugin.Options}" ScrollViewer.HorizontalScrollBarVisibility="Disabled" Padding="8" BorderBrush="Transparent"> <ListBox Grid.Row="1" Name="lstOptions" ItemsSource="{Binding Plugin.Options}" ScrollViewer.HorizontalScrollBarVisibility="Disabled" Padding="8" BorderBrush="Transparent" ItemContainerStyle="{StaticResource OptionItemStyle}">
<ListBox.ItemTemplateSelector> <ListBox.ItemTemplateSelector>
<local:OptionTemplateSelector TextTemplate="{StaticResource TextTemplate}" <local:OptionTemplateSelector TextTemplate="{StaticResource TextTemplate}"
FilePathTemplate="{StaticResource FilePathTemplate}" FilePathTemplate="{StaticResource FilePathTemplate}"
NumberTemplate="{StaticResource NumberTemplate}" NumberDecimalTemplate="{StaticResource NumberDecimalTemplate}"
NumberHexTemplate="{StaticResource NumberHexTemplate}"
BooleanTemplate="{StaticResource BooleanTemplate}" BooleanTemplate="{StaticResource BooleanTemplate}"
ChoiceTemplate="{StaticResource ChoiceTemplate}"> ChoiceDropdownTemplate="{StaticResource ChoiceDropdownTemplate}"
ChoiceListTemplate="{StaticResource ChoiceListTemplate}">
</local:OptionTemplateSelector> </local:OptionTemplateSelector>
</ListBox.ItemTemplateSelector> </ListBox.ItemTemplateSelector>
</ListBox> </ListBox>

View File

@@ -9,6 +9,7 @@ using System.Collections.Generic;
using System.Text; using System.Text;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data; using System.Windows.Data;
using System.Windows.Documents; using System.Windows.Documents;
using System.Windows.Input; using System.Windows.Input;
@@ -16,30 +17,33 @@ using System.Windows.Media;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using System.Windows.Shapes; using System.Windows.Shapes;
using Microsoft.Win32; using Microsoft.Win32;
using Il2CppInspectorGUI;
using System.Windows.Forms.Design; using System.Windows.Forms.Design;
using System.ComponentModel; using System.ComponentModel;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Linq; using System.Linq;
using Il2CppInspector.PluginAPI.V100; using Il2CppInspector.PluginAPI.V100;
using static Il2CppInspector.PluginManager; using Il2CppInspector.Reflection;
namespace Il2CppInspector.GUI namespace Il2CppInspectorGUI
{ {
// Class which selects the correct control to display for each plugin option // Class which selects the correct control to display for each plugin option
public class OptionTemplateSelector : DataTemplateSelector public class OptionTemplateSelector : DataTemplateSelector
{ {
public DataTemplate TextTemplate { get; set; } public DataTemplate TextTemplate { get; set; }
public DataTemplate FilePathTemplate { get; set; } public DataTemplate FilePathTemplate { get; set; }
public DataTemplate NumberTemplate { get; set; } public DataTemplate NumberDecimalTemplate { get; set; }
public DataTemplate NumberHexTemplate { get; set; }
public DataTemplate BooleanTemplate { get; set; } public DataTemplate BooleanTemplate { get; set; }
public DataTemplate ChoiceTemplate { get; set; } public DataTemplate ChoiceDropdownTemplate { get; set; }
public DataTemplate ChoiceListTemplate { get; set; }
// Use some fancy reflection to get the right template property // Use some fancy reflection to get the right template property
// If the plugin option is PluginOptionFooBar and its style enum property is Baz, the template will be FooBarBazTemplate
public override DataTemplate SelectTemplate(object item, DependencyObject container) { public override DataTemplate SelectTemplate(object item, DependencyObject container) {
var option = (IPluginOption) item; var option = (IPluginOption) item;
return (DataTemplate) GetType().GetProperty(option.GetType().Name.Split("`")[0]["PluginOption".Length..] + "Template").GetValue(this); var style = item.GetType().GetProperty("Style")?.GetValue(item).ToString() ?? string.Empty;
return (DataTemplate) GetType().GetProperty(option.GetType().Name.Split("`")[0]["PluginOption".Length..] + style + "Template").GetValue(this);
} }
} }
@@ -51,14 +55,76 @@ namespace Il2CppInspector.GUI
// Item to configure // Item to configure
public IPlugin Plugin { get; } public IPlugin Plugin { get; }
// This helps us find XAML elements withing a DataTemplate
// Adapted from https://docs.microsoft.com/en-us/dotnet/desktop/wpf/data/how-to-find-datatemplate-generated-elements?view=netframeworkdesktop-4.8
private childItem FindVisualChild<childItem>(DependencyObject obj) where childItem : DependencyObject {
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++) {
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child != null && child is childItem) {
return (childItem) child;
} else {
childItem childOfChild = FindVisualChild<childItem>(child);
if (childOfChild != null)
return childOfChild;
}
}
return null;
}
// Adapted from https://stackoverflow.com/a/565560
// The dependency object is valid if it has no errors and all
// of its children (that are dependency objects) are error-free.
private bool IsValid(DependencyObject obj)
=> !Validation.GetHasError(obj) && Enumerable.Range(0, VisualTreeHelper.GetChildrenCount(obj)).All(n => IsValid(VisualTreeHelper.GetChild(obj, n)));
// Adapted from https://stackoverflow.com/questions/22510428/get-listboxitem-from-listbox
// In order to force validation with ExceptionValidationRule when the window opens,
// we need to wait until all of the ListBoxItems are populated, then find the element with the value
// then force WPF to try to update the source property to see if it raises an exception
// and cause the DataTriggers to execute.
// This relies on a 'valueControl' named element existing.
void OptionsListBoxStatusChanged(object sender, EventArgs e) {
// Wait for items to be generated
if (lstOptions.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated)
return;
// Remove event
lstOptions.ItemContainerGenerator.StatusChanged -= OptionsListBoxStatusChanged;
// your items are now generated
// Adapted from https://stackoverflow.com/a/18008545
foreach (var item in lstOptions.Items) {
var listBoxItem = lstOptions.ItemContainerGenerator.ContainerFromItem(item);
var presenter = FindVisualChild<ContentPresenter>(listBoxItem);
var dataTemplate = presenter.ContentTemplateSelector.SelectTemplate(item, listBoxItem);
var boundControl = dataTemplate.FindName("valueControl", presenter);
// Adapted from https://stackoverflow.com/questions/794370/update-all-bindings-in-usercontrol-at-once
((FrameworkElement) boundControl).GetBindingExpression(boundControl switch {
TextBox t => TextBox.TextProperty,
CheckBox c => CheckBox.IsCheckedProperty,
ListBox l => ListBox.SelectedValueProperty,
ComboBox m => ComboBox.SelectedValueProperty,
TextBlock b => TextBlock.TextProperty,
_ => throw new InvalidOperationException("Unknown value control type")
}).UpdateSource();
}
}
// Initialize configuration dialog window
public PluginConfigurationDialog(IPlugin plugin) { public PluginConfigurationDialog(IPlugin plugin) {
InitializeComponent(); InitializeComponent();
DataContext = this; DataContext = this;
Plugin = plugin; Plugin = plugin;
// Validate options once they have loaded
lstOptions.ItemContainerGenerator.StatusChanged += OptionsListBoxStatusChanged;
} }
private void okButton_Click(object sender, RoutedEventArgs e) { private void okButton_Click(object sender, RoutedEventArgs e) {
// Closes dialog box automatically // Close dialog box but call OnClosing first to validate all the options
DialogResult = true; DialogResult = true;
// TODO: Plugin hook OptionsChanged (and make sure it works when clicking close icon as well) // TODO: Plugin hook OptionsChanged (and make sure it works when clicking close icon as well)
@@ -73,17 +139,21 @@ namespace Il2CppInspector.GUI
Title = option.Description, Title = option.Description,
Filter = "All files (*.*)|*.*", Filter = "All files (*.*)|*.*",
FileName = option.Value, FileName = option.Value,
CheckFileExists = true CheckFileExists = false,
CheckPathExists = false
}; };
if (openFileDialog.ShowDialog() == true) { if (openFileDialog.ShowDialog() == true) {
option.Value = openFileDialog.FileName;
// This spaghetti saves us from implementing INotifyPropertyChanged on Plugin.Options // This spaghetti saves us from implementing INotifyPropertyChanged on Plugin.Options
// (we don't want to expose WPF stuff in our SDK) // (we don't want to expose WPF stuff in our SDK)
// Will break if we change the format of the FilePathDataTemplate too much // Will break if we change the format of the FilePathDataTemplate too much
var tb = ((DockPanel) ((Button) sender).Parent).Children.OfType<TextBlock>().First(n => n.Name == "txtFilePathSelector"); var tb = ((DockPanel) ((Button) sender).Parent).Children.OfType<StackPanel>().First().Children.OfType<TextBox>().First(n => n.Name == "valueControl");
tb.Text = option.Value; try {
tb.Clear();
tb.AppendText(openFileDialog.FileName);
tb.GetBindingExpression(TextBox.TextProperty).UpdateSource();
}
catch { }
} }
} }
@@ -91,5 +161,13 @@ namespace Il2CppInspector.GUI
private void txtHexString_PreviewTextInput(object sender, TextCompositionEventArgs e) { private void txtHexString_PreviewTextInput(object sender, TextCompositionEventArgs e) {
e.Handled = !Regex.IsMatch(e.Text, @"[A-Fa-f0-9]"); e.Handled = !Regex.IsMatch(e.Text, @"[A-Fa-f0-9]");
} }
// Check options validity before allowing the dialog to close either by clicking OK or the close icon
private void Window_Closing(object sender, CancelEventArgs e) {
if (!IsValid(lstOptions)) {
MessageBox.Show("One or more options are invalid.", "Il2CppInspector Plugin Configuration");
e.Cancel = true;
}
}
} }
} }

View File

@@ -46,7 +46,7 @@ namespace Il2CppInspector.GUI
private void btnConfig_Click(object sender, RoutedEventArgs e) { private void btnConfig_Click(object sender, RoutedEventArgs e) {
var plugin = (ManagedPlugin) ((Button) sender).DataContext; var plugin = (ManagedPlugin) ((Button) sender).DataContext;
var configDlg = new PluginConfigurationDialog(plugin.Plugin); var configDlg = new Il2CppInspectorGUI.PluginConfigurationDialog(plugin.Plugin);
configDlg.Owner = this; configDlg.Owner = this;
configDlg.ShowDialog(); configDlg.ShowDialog();
} }