replace v6 with experimental v7 code
This commit is contained in:
20
src/MpvNet.Windows/WPF/BindingProxy.cs
Normal file
20
src/MpvNet.Windows/WPF/BindingProxy.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
|
||||
using System.Windows;
|
||||
|
||||
namespace MpvNet.Windows.WPF;
|
||||
|
||||
public class BindingProxy : Freezable
|
||||
{
|
||||
protected override Freezable CreateInstanceCore() => new BindingProxy();
|
||||
|
||||
public object Data
|
||||
{
|
||||
get { return GetValue(DataProperty); }
|
||||
set { SetValue(DataProperty, value); }
|
||||
}
|
||||
|
||||
// Using a DependencyProperty as the backing store for Data.
|
||||
// This enables animation, styling, binding, etc...
|
||||
public static readonly DependencyProperty DataProperty =
|
||||
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
|
||||
}
|
||||
162
src/MpvNet.Windows/WPF/ConfWindow.xaml
Normal file
162
src/MpvNet.Windows/WPF/ConfWindow.xaml
Normal file
@@ -0,0 +1,162 @@
|
||||
<Window
|
||||
x:Name="ConfWindow1"
|
||||
x:Class="MpvNet.Windows.WPF.ConfWindow"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
|
||||
xmlns:controls="clr-namespace:MpvNet.Windows.WPF.Controls"
|
||||
xmlns:wpf="clr-namespace:MpvNet.Windows.WPF"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
mc:Ignorable="d"
|
||||
|
||||
Title="Config Editor"
|
||||
Height="550"
|
||||
Width="800"
|
||||
Foreground="{Binding Theme.Foreground}"
|
||||
Background="{Binding Theme.Background}"
|
||||
ShowInTaskbar="False"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
Loaded="ConfWindow1_Loaded"
|
||||
>
|
||||
|
||||
<Window.Resources>
|
||||
<wpf:BindingProxy x:Key="BindingProxy" Data="{Binding}" />
|
||||
</Window.Resources>
|
||||
|
||||
<Window.InputBindings>
|
||||
<KeyBinding Key="n" Modifiers="Ctrl" Command="{Binding ShowMpvNetSpecificSettingsCommand}"/>
|
||||
<KeyBinding Key="F5" Command="{Binding PreviewMpvConfFileCommand}"/>
|
||||
<KeyBinding Key="F6" Command="{Binding PreviewMpvNetConfFileCommand}"/>
|
||||
<KeyBinding Key="F1" Command="{Binding ShowMpvManualCommand}"/>
|
||||
<KeyBinding Key="F2" Command="{Binding ShowMpvNetManualCommand}"/>
|
||||
</Window.InputBindings>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="170" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<controls:SearchControl
|
||||
x:Name="SearchControl"
|
||||
HintText="Find a setting"
|
||||
Margin="20,20,0,10"
|
||||
Text="{Binding SearchText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
/>
|
||||
|
||||
<ScrollViewer
|
||||
Name="MainScrollViewer"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="2"
|
||||
Margin="0,0,0,10"
|
||||
>
|
||||
|
||||
<StackPanel x:Name="MainStackPanel"></StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
<TreeView
|
||||
x:Name="TreeView"
|
||||
ItemsSource="{Binding Nodes}"
|
||||
Margin="20,0,0,0"
|
||||
Grid.Row="1"
|
||||
BorderThickness="0"
|
||||
Foreground="{Binding Theme.Foreground}"
|
||||
Background="{Binding Theme.Background}"
|
||||
SelectedItemChanged="TreeView_SelectedItemChanged"
|
||||
>
|
||||
|
||||
<TreeView.Resources>
|
||||
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}" Color="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Theme.Background}" />
|
||||
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Theme.Background}" />
|
||||
</TreeView.Resources>
|
||||
|
||||
<TreeView.ItemContainerStyle>
|
||||
<Style TargetType="{x:Type TreeViewItem}">
|
||||
<Setter Property="Cursor" Value="Hand" />
|
||||
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
|
||||
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
|
||||
<Setter Property="FontWeight" Value="Normal" />
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="Foreground" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Theme.Foreground2}" />
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter Property="FontWeight" Value="Bold" />
|
||||
<Setter Property="Background" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Theme.Background2}" />
|
||||
<Setter Property="Foreground" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Theme.Foreground}" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TreeView.ItemContainerStyle>
|
||||
|
||||
<TreeView.ItemTemplate>
|
||||
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
|
||||
<TextBlock Text="{Binding Name}" />
|
||||
</HierarchicalDataTemplate>
|
||||
</TreeView.ItemTemplate>
|
||||
</TreeView>
|
||||
|
||||
<TextBlock
|
||||
Name="MenuTextBlock"
|
||||
Text="Menu"
|
||||
Cursor="Hand"
|
||||
Foreground="LightGray"
|
||||
TextDecorations="Underline"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="20,5,10,10"
|
||||
Grid.Row="2"
|
||||
>
|
||||
|
||||
<TextBlock.ContextMenu>
|
||||
<ContextMenu Name="MainContextMenu">
|
||||
<MenuItem
|
||||
Header="Show mpv.net options"
|
||||
InputGestureText="Ctrl+n"
|
||||
Command="{Binding Data.ShowMpvNetSpecificSettingsCommand, Source={StaticResource BindingProxy}}"
|
||||
/>
|
||||
<Separator />
|
||||
<MenuItem
|
||||
Header="Preview mpv.conf"
|
||||
InputGestureText="F5"
|
||||
Command="{Binding Data.PreviewMpvConfFileCommand, Source={StaticResource BindingProxy}}"
|
||||
/>
|
||||
<MenuItem
|
||||
Header="Preview mpvnet.conf"
|
||||
InputGestureText="F6"
|
||||
Command="{Binding Data.PreviewMpvNetConfFileCommand, Source={StaticResource BindingProxy}}"
|
||||
/>
|
||||
<Separator />
|
||||
<MenuItem
|
||||
Header="Show mpv manual"
|
||||
InputGestureText="F1"
|
||||
Command="{Binding Data.ShowMpvManualCommand, Source={StaticResource BindingProxy}}"
|
||||
/>
|
||||
<MenuItem
|
||||
Header="Show mpv.net manual"
|
||||
InputGestureText="F2"
|
||||
Command="{Binding Data.ShowMpvNetManualCommand, Source={StaticResource BindingProxy}}"
|
||||
/>
|
||||
</ContextMenu>
|
||||
</TextBlock.ContextMenu>
|
||||
|
||||
<b:Interaction.Triggers>
|
||||
<b:EventTrigger EventName="MouseLeftButtonDown">
|
||||
<b:ChangePropertyAction TargetObject="{Binding ContextMenu, ElementName=MenuTextBlock}"
|
||||
PropertyName="PlacementTarget"
|
||||
Value="{Binding ElementName=MenuTextBlock, Mode=OneWay}"/>
|
||||
<b:ChangePropertyAction TargetObject="{Binding ContextMenu, ElementName=MenuTextBlock}"
|
||||
PropertyName="IsOpen"
|
||||
Value="True"/>
|
||||
</b:EventTrigger>
|
||||
</b:Interaction.Triggers>
|
||||
</TextBlock>
|
||||
|
||||
</Grid>
|
||||
</Window>
|
||||
482
src/MpvNet.Windows/WPF/ConfWindow.xaml.cs
Normal file
482
src/MpvNet.Windows/WPF/ConfWindow.xaml.cs
Normal file
@@ -0,0 +1,482 @@
|
||||
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Threading;
|
||||
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
using MpvNet.Help;
|
||||
using MpvNet.Windows.UI;
|
||||
using MpvNet.Windows.WPF.Controls;
|
||||
using MpvNet.Windows.WPF.ViewModels;
|
||||
|
||||
namespace MpvNet.Windows.WPF;
|
||||
|
||||
public partial class ConfWindow : Window, INotifyPropertyChanged
|
||||
{
|
||||
List<Setting> Settings = Conf.LoadConf(Properties.Resources.editor_conf.TrimEnd());
|
||||
List<ConfItem> ConfItems = new List<ConfItem>();
|
||||
public ObservableCollection<string> FilterStrings { get; } = new();
|
||||
string InitialContent;
|
||||
string ThemeConf = GetThemeConf();
|
||||
string? _searchText;
|
||||
List<NodeViewModel>? _nodes;
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
public ConfWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = this;
|
||||
LoadConf(Player.ConfPath);
|
||||
LoadConf(App.ConfPath);
|
||||
LoadSettings();
|
||||
InitialContent = GetCompareString();
|
||||
|
||||
if (string.IsNullOrEmpty(App.Settings.ConfigEditorSearch))
|
||||
SearchControl.Text = "General:";
|
||||
else
|
||||
SearchControl.Text = App.Settings.ConfigEditorSearch;
|
||||
|
||||
foreach (var node in Nodes)
|
||||
SelectNodeFromSearchText(node);
|
||||
|
||||
foreach (var node in Nodes)
|
||||
ExpandNode(node);
|
||||
}
|
||||
|
||||
public Theme? Theme => Theme.Current;
|
||||
|
||||
public string SearchText
|
||||
{
|
||||
get => _searchText ?? "";
|
||||
set
|
||||
{
|
||||
_searchText = value;
|
||||
SearchTextChanged();
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public List<NodeViewModel> Nodes
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_nodes == null)
|
||||
{
|
||||
var rootNode = new TreeNode();
|
||||
|
||||
foreach (Setting setting in Settings)
|
||||
AddNode(rootNode.Children, setting.Directory!);
|
||||
|
||||
_nodes = new NodeViewModel(rootNode).Children;
|
||||
}
|
||||
|
||||
return _nodes;
|
||||
}
|
||||
}
|
||||
|
||||
public static TreeNode? AddNode(IList<TreeNode> nodes, string path)
|
||||
{
|
||||
string[] parts = path.Split(new[] { "/" }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
for (int x = 0; x < parts.Length; x++)
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
if (x < parts.Length - 1)
|
||||
{
|
||||
if (node.Name == parts[x])
|
||||
{
|
||||
found = true;
|
||||
nodes = node.Children;
|
||||
}
|
||||
}
|
||||
else if (x == parts.Length - 1 && node.Name == parts[x])
|
||||
{
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
if (x == parts.Length - 1)
|
||||
{
|
||||
var item = new TreeNode() { Name = parts[x] };
|
||||
nodes?.Add(item);
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
void LoadSettings()
|
||||
{
|
||||
foreach (Setting setting in Settings)
|
||||
{
|
||||
setting.StartValue = setting.Value;
|
||||
|
||||
if (!FilterStrings.Contains(setting.Directory!))
|
||||
FilterStrings.Add(setting.Directory!);
|
||||
|
||||
foreach (ConfItem confItem in ConfItems)
|
||||
{
|
||||
if (setting.Name == confItem.Name && confItem.Section == "" && !confItem.IsSectionItem)
|
||||
{
|
||||
setting.Value = confItem.Value.Trim('\'', '"');
|
||||
setting.StartValue = setting.Value;
|
||||
setting.ConfItem = confItem;
|
||||
confItem.SettingBase = setting;
|
||||
}
|
||||
}
|
||||
|
||||
switch (setting)
|
||||
{
|
||||
case StringSetting s:
|
||||
MainStackPanel.Children.Add(new StringSettingControl(s) { Visibility = Visibility.Collapsed });
|
||||
break;
|
||||
case OptionSetting s:
|
||||
MainStackPanel.Children.Add(new OptionSettingControl(s) { Visibility = Visibility.Collapsed });
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static string GetThemeConf() => Theme.DarkMode + App.DarkTheme + App.LightTheme;
|
||||
|
||||
protected override void OnClosed(EventArgs e)
|
||||
{
|
||||
base.OnClosed(e);
|
||||
App.Settings.ConfigEditorSearch = SearchControl.Text;
|
||||
|
||||
if (InitialContent == GetCompareString())
|
||||
return;
|
||||
|
||||
File.WriteAllText(Player.ConfPath, GetContent("mpv"));
|
||||
File.WriteAllText(App.ConfPath, GetContent("mpvnet"));
|
||||
|
||||
foreach (Setting it in Settings)
|
||||
{
|
||||
if (it.Value != it.StartValue)
|
||||
{
|
||||
if (it.File == "mpv")
|
||||
{
|
||||
Player.ProcessProperty(it.Name, it.Value);
|
||||
Player.SetPropertyString(it.Name!, it.Value!);
|
||||
}
|
||||
else if (it.File == "mpvnet")
|
||||
App.ProcessProperty(it.Name ?? "", it.Value ?? "", true);
|
||||
}
|
||||
}
|
||||
|
||||
Theme.Init();
|
||||
Theme.UpdateWpfColors();
|
||||
|
||||
if (ThemeConf != GetThemeConf())
|
||||
MessageBox.Show("Changed theme settings require mpv.net being restarted.", "Info");
|
||||
}
|
||||
|
||||
bool _shown;
|
||||
|
||||
protected override void OnContentRendered(EventArgs e)
|
||||
{
|
||||
base.OnContentRendered(e);
|
||||
|
||||
if (_shown)
|
||||
return;
|
||||
|
||||
_shown = true;
|
||||
|
||||
Application.Current.Dispatcher.BeginInvoke(() => {
|
||||
SearchControl.SearchTextBox.SelectAll();
|
||||
},
|
||||
DispatcherPriority.Background);
|
||||
}
|
||||
|
||||
string GetCompareString()
|
||||
{
|
||||
return string.Join("", Settings.Select(item => item.Name + item.Value).ToArray());
|
||||
}
|
||||
|
||||
void LoadConf(string file)
|
||||
{
|
||||
if (!File.Exists(file))
|
||||
return;
|
||||
|
||||
string comment = "";
|
||||
string section = "";
|
||||
|
||||
bool isSectionItem = false;
|
||||
|
||||
foreach (string currentLine in File.ReadAllLines(file))
|
||||
{
|
||||
string line = currentLine.Trim();
|
||||
|
||||
if (line == "")
|
||||
{
|
||||
comment += "\r\n";
|
||||
}
|
||||
else if (line.StartsWith("#"))
|
||||
{
|
||||
comment += line.Trim() + "\r\n";
|
||||
}
|
||||
else if (line.StartsWith("[") && line.Contains("]"))
|
||||
{
|
||||
if (!isSectionItem && comment != "" && comment != "\r\n")
|
||||
ConfItems.Add(new ConfItem() {
|
||||
Comment = comment, File = Path.GetFileNameWithoutExtension(file)});
|
||||
|
||||
section = line.Substring(0, line.IndexOf("]") + 1);
|
||||
comment = "";
|
||||
isSectionItem = true;
|
||||
}
|
||||
else if (line.Contains("="))
|
||||
{
|
||||
ConfItem item = new ConfItem();
|
||||
item.File = Path.GetFileNameWithoutExtension(file);
|
||||
item.IsSectionItem = isSectionItem;
|
||||
item.Comment = comment;
|
||||
comment = "";
|
||||
item.Section = section;
|
||||
section = "";
|
||||
|
||||
if (line.Contains("#") && !line.Contains("'") && !line.Contains("\""))
|
||||
{
|
||||
item.LineComment = line.Substring(line.IndexOf("#")).Trim();
|
||||
line = line.Substring(0, line.IndexOf("#")).Trim();
|
||||
}
|
||||
|
||||
int pos = line.IndexOf("=");
|
||||
string left = line.Substring(0, pos).Trim().ToLower();
|
||||
string right = line.Substring(pos + 1).Trim();
|
||||
|
||||
if (left == "fs")
|
||||
left = "fullscreen";
|
||||
|
||||
if (left == "loop")
|
||||
left = "loop-file";
|
||||
|
||||
item.Name = left;
|
||||
item.Value = right;
|
||||
ConfItems.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string GetContent(string filename)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
List<string> namesWritten = new List<string>();
|
||||
|
||||
foreach (ConfItem item in ConfItems)
|
||||
{
|
||||
if (filename != item.File || item.Section != "" || item.IsSectionItem)
|
||||
continue;
|
||||
|
||||
if (item.Comment != "")
|
||||
sb.Append(item.Comment);
|
||||
|
||||
if (item.SettingBase == null)
|
||||
{
|
||||
if (item.Name != "")
|
||||
{
|
||||
sb.Append(item.Name + " = " + item.Value);
|
||||
|
||||
if (item.LineComment != "")
|
||||
sb.Append(" " + item.LineComment);
|
||||
|
||||
sb.AppendLine();
|
||||
namesWritten.Add(item.Name);
|
||||
}
|
||||
}
|
||||
else if ((item.SettingBase.Value ?? "") != item.SettingBase.Default)
|
||||
{
|
||||
string? value;
|
||||
|
||||
if (item.SettingBase.Type == "string" ||
|
||||
item.SettingBase.Type == "folder" ||
|
||||
item.SettingBase.Type == "color")
|
||||
|
||||
value = "'" + item.SettingBase.Value + "'";
|
||||
else
|
||||
value = item.SettingBase.Value;
|
||||
|
||||
sb.Append(item.Name + " = " + value);
|
||||
|
||||
if (item.LineComment != "")
|
||||
sb.Append(" " + item.LineComment);
|
||||
|
||||
sb.AppendLine();
|
||||
namesWritten.Add(item.Name);
|
||||
}
|
||||
}
|
||||
|
||||
if (!sb.ToString().Contains("# Editor"))
|
||||
sb.AppendLine("# Editor");
|
||||
|
||||
foreach (Setting setting in Settings)
|
||||
{
|
||||
if (filename != setting.File || namesWritten.Contains(setting.Name!))
|
||||
continue;
|
||||
|
||||
if ((setting.Value ?? "") != setting.Default)
|
||||
{
|
||||
string? value;
|
||||
|
||||
if (setting.Type == "string" ||
|
||||
setting.Type == "folder" ||
|
||||
setting.Type == "color")
|
||||
|
||||
value = "'" + setting.Value + "'";
|
||||
else
|
||||
value = setting.Value;
|
||||
|
||||
sb.AppendLine(setting.Name + " = " + value);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (ConfItem item in ConfItems)
|
||||
{
|
||||
if (filename != item.File || (item.Section == "" && !item.IsSectionItem))
|
||||
continue;
|
||||
|
||||
if (item.Section != "")
|
||||
{
|
||||
if (!sb.ToString().EndsWith("\r\n\r\n"))
|
||||
sb.AppendLine();
|
||||
|
||||
sb.AppendLine(item.Section);
|
||||
}
|
||||
|
||||
if (item.Comment != "")
|
||||
sb.Append(item.Comment);
|
||||
|
||||
sb.Append(item.Name + " = " + item.Value);
|
||||
|
||||
if (item.LineComment != "")
|
||||
sb.Append(" " + item.LineComment);
|
||||
|
||||
sb.AppendLine();
|
||||
namesWritten.Add(item.Name);
|
||||
}
|
||||
|
||||
return "\r\n" + sb.ToString().Trim() + "\r\n";
|
||||
}
|
||||
|
||||
void SearchTextChanged()
|
||||
{
|
||||
string activeFilter = "";
|
||||
|
||||
foreach (string i in FilterStrings)
|
||||
if (SearchText == i + ":")
|
||||
activeFilter = i;
|
||||
|
||||
if (activeFilter == "")
|
||||
{
|
||||
foreach (UIElement i in MainStackPanel.Children)
|
||||
if ((i as ISettingControl)!.Contains(SearchText) && SearchText.Length > 1)
|
||||
i.Visibility = Visibility.Visible;
|
||||
else
|
||||
i.Visibility = Visibility.Collapsed;
|
||||
|
||||
foreach (var node in Nodes)
|
||||
UnselectNode(node);
|
||||
}
|
||||
else
|
||||
foreach (UIElement i in MainStackPanel.Children)
|
||||
if ((i as ISettingControl)!.Setting.Directory == activeFilter)
|
||||
i.Visibility = Visibility.Visible;
|
||||
else
|
||||
i.Visibility = Visibility.Collapsed;
|
||||
|
||||
MainScrollViewer.ScrollToTop();
|
||||
}
|
||||
|
||||
void ConfWindow1_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SearchControl.SearchTextBox.SelectAll();
|
||||
Keyboard.Focus(SearchControl.SearchTextBox);
|
||||
|
||||
foreach (var i in MainStackPanel.Children.OfType<StringSettingControl>())
|
||||
i.Update();
|
||||
}
|
||||
|
||||
protected override void OnKeyDown(KeyEventArgs e)
|
||||
{
|
||||
base.OnKeyDown(e);
|
||||
|
||||
if (e.Key == Key.Escape)
|
||||
Close();
|
||||
|
||||
if (e.Key == Key.F3 || e.Key == Key.F6 || (e.Key == Key.F && Keyboard.IsKeyDown(Key.LeftCtrl)))
|
||||
{
|
||||
Keyboard.Focus(SearchControl.SearchTextBox);
|
||||
SearchControl.SearchTextBox.SelectAll();
|
||||
}
|
||||
}
|
||||
|
||||
protected void OnPropertyChanged([CallerMemberName] string? name = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
|
||||
}
|
||||
|
||||
void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
|
||||
{
|
||||
var node = TreeView.SelectedItem as NodeViewModel;
|
||||
|
||||
if (node == null)
|
||||
return;
|
||||
|
||||
Application.Current.Dispatcher.BeginInvoke(() => {
|
||||
SearchText = node!.Path + ":";
|
||||
},
|
||||
DispatcherPriority.Background);
|
||||
}
|
||||
|
||||
void SelectNodeFromSearchText(NodeViewModel node)
|
||||
{
|
||||
if (node.Path + ":" == SearchControl.Text)
|
||||
{
|
||||
node.IsSelected = true;
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var it in node.Children)
|
||||
SelectNodeFromSearchText(it);
|
||||
}
|
||||
|
||||
void UnselectNode(NodeViewModel node)
|
||||
{
|
||||
if (node.IsSelected)
|
||||
node.IsSelected = false;
|
||||
|
||||
foreach (var it in node.Children)
|
||||
SelectNodeFromSearchText(it);
|
||||
}
|
||||
|
||||
void ExpandNode(NodeViewModel node)
|
||||
{
|
||||
node.IsExpanded = true;
|
||||
|
||||
foreach (var it in node.Children)
|
||||
SelectNodeFromSearchText(it);
|
||||
}
|
||||
|
||||
[RelayCommand] void ShowMpvNetSpecificSettings() => SearchControl.Text = "mpv.net";
|
||||
|
||||
[RelayCommand] void PreviewMpvConfFile() => Msg.ShowInfo(GetContent("mpv"));
|
||||
|
||||
[RelayCommand] void PreviewMpvNetConfFile() => Msg.ShowInfo(GetContent("mpvnet"));
|
||||
|
||||
[RelayCommand] void ShowMpvManual() => ProcessHelp.ShellExecute("https://mpv.io/manual/master/");
|
||||
|
||||
[RelayCommand] void ShowMpvNetManual() => ProcessHelp.ShellExecute("https://github.com/mpvnet-player/mpv.net/blob/master/docs/manual.md");
|
||||
}
|
||||
126
src/MpvNet.Windows/WPF/Controls/CommandPaletteControl.xaml
Normal file
126
src/MpvNet.Windows/WPF/Controls/CommandPaletteControl.xaml
Normal file
@@ -0,0 +1,126 @@
|
||||
<UserControl
|
||||
x:Class="MpvNet.Windows.WPF.Controls.CommandPaletteControl"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:controls="clr-namespace:MpvNet.Windows.WPF.Controls"
|
||||
mc:Ignorable="d"
|
||||
|
||||
FontSize="13"
|
||||
Loaded="OnLoaded"
|
||||
Background="#111111"
|
||||
>
|
||||
|
||||
<UserControl.InputBindings>
|
||||
<KeyBinding Gesture="Esc" Command="{Binding EscapeCommand}"/>
|
||||
<KeyBinding Gesture="Enter" Command="{Binding ExecuteCommand}"/>
|
||||
</UserControl.InputBindings>
|
||||
|
||||
<Border Name="MainBorder"
|
||||
BorderThickness="1,0,1,1"
|
||||
CornerRadius="0,0,5,5"
|
||||
Padding="0,0,0,5"
|
||||
BorderBrush="{Binding Theme.MenuHighlight}"
|
||||
Background="{Binding Theme.Background}"
|
||||
SnapsToDevicePixels="True"
|
||||
>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Border BorderBrush="{Binding Theme.Heading}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="3"
|
||||
Margin="7"
|
||||
>
|
||||
|
||||
<controls:SearchControl
|
||||
HintText="Search"
|
||||
x:Name="SearchControl"
|
||||
Grid.ColumnSpan="2"
|
||||
Padding="1,1,1,0"
|
||||
/>
|
||||
</Border>
|
||||
|
||||
<ListView Name="MainListView"
|
||||
Grid.Row="1"
|
||||
Foreground="{Binding Theme.Foreground}"
|
||||
Background="{Binding Theme.Background}"
|
||||
BorderThickness="0"
|
||||
MaxHeight="202"
|
||||
SizeChanged="MainListView_SizeChanged"
|
||||
MouseUp="MainListView_MouseUp"
|
||||
>
|
||||
|
||||
<ListView.ItemContainerStyle>
|
||||
<Style TargetType="ListBoxItem">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
|
||||
<Setter Property="Height" Value="25"></Setter>
|
||||
<Setter Property="BorderThickness" Value="0"></Setter>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type ListBoxItem}">
|
||||
<Border x:Name="BD"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
Background="{TemplateBinding Background}"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
SnapsToDevicePixels="true">
|
||||
|
||||
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<MultiTrigger>
|
||||
<MultiTrigger.Conditions>
|
||||
<Condition Property="IsMouseOver" Value="True" />
|
||||
</MultiTrigger.Conditions>
|
||||
<Setter Property="Background" TargetName="BD" Value="{DynamicResource HighlightBrush}" />
|
||||
</MultiTrigger>
|
||||
<MultiTrigger>
|
||||
<MultiTrigger.Conditions>
|
||||
<Condition Property="Selector.IsSelectionActive" Value="False" />
|
||||
<Condition Property="IsSelected" Value="True" />
|
||||
</MultiTrigger.Conditions>
|
||||
<Setter Property="Background" TargetName="BD" Value="{DynamicResource BorderBrush}" />
|
||||
</MultiTrigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
|
||||
<Style.Resources>
|
||||
<Style TargetType="Border">
|
||||
<Setter Property="CornerRadius" Value="3"/>
|
||||
</Style>
|
||||
</Style.Resources>
|
||||
</Style>
|
||||
</ListView.ItemContainerStyle>
|
||||
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Text="{Binding Text}"></TextBlock>
|
||||
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
Text="{Binding SecondaryText}"
|
||||
HorizontalAlignment="Right"
|
||||
/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</Grid>
|
||||
</Border>
|
||||
</UserControl>
|
||||
151
src/MpvNet.Windows/WPF/Controls/CommandPaletteControl.xaml.cs
Normal file
151
src/MpvNet.Windows/WPF/Controls/CommandPaletteControl.xaml.cs
Normal file
@@ -0,0 +1,151 @@
|
||||
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
using MpvNet.Windows.UI;
|
||||
using MpvNet.Windows.WinForms;
|
||||
|
||||
namespace MpvNet.Windows.WPF.Controls;
|
||||
|
||||
public partial class CommandPaletteControl : UserControl
|
||||
{
|
||||
public ICollectionView CollectionView { get; set; }
|
||||
public CollectionViewSource CollectionViewSource { get; }
|
||||
public ObservableCollection<CommandPaletteItem> Items { get; } = new ObservableCollection<CommandPaletteItem>();
|
||||
|
||||
public CommandPaletteControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = this;
|
||||
CollectionViewSource = new CollectionViewSource() { Source = Items };
|
||||
CollectionView = CollectionViewSource.View;
|
||||
CollectionView.Filter = new Predicate<object>(item => Filter((CommandPaletteItem)item));
|
||||
MainListView.ItemsSource = CollectionView;
|
||||
|
||||
SearchControl.SearchTextBox.PreviewKeyDown += SearchTextBox_PreviewKeyDown;
|
||||
SearchControl.SearchTextBox.TextChanged += SearchTextBox_TextChanged;
|
||||
SearchControl.HideClearButton = true;
|
||||
|
||||
if (Environment.OSVersion.Version < new Version(10, 0))
|
||||
MainBorder.CornerRadius = new CornerRadius(0);
|
||||
}
|
||||
|
||||
void SearchTextBox_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
CollectionView.Refresh();
|
||||
SelectFirst();
|
||||
}
|
||||
|
||||
void SearchTextBox_PreviewKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
switch (e.Key)
|
||||
{
|
||||
case Key.Up:
|
||||
{
|
||||
int index = MainListView.SelectedIndex;
|
||||
index -= 1;
|
||||
|
||||
if (index < 0)
|
||||
index = 0;
|
||||
|
||||
MainListView.SelectedIndex = index;
|
||||
MainListView.ScrollIntoView(MainListView.SelectedItem);
|
||||
}
|
||||
break;
|
||||
case Key.Down:
|
||||
{
|
||||
int index = MainListView.SelectedIndex;
|
||||
|
||||
if (++index > MainListView.Items.Count - 1)
|
||||
index = MainListView.Items.Count - 1;
|
||||
|
||||
MainListView.SelectedIndex = index;
|
||||
MainListView.ScrollIntoView(MainListView.SelectedItem);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MainListView_SizeChanged(object sender, SizeChangedEventArgs e) => AdjustHeight();
|
||||
|
||||
void MainListView_MouseUp(object sender, MouseButtonEventArgs e) => ExecuteInternal();
|
||||
|
||||
[RelayCommand]
|
||||
void Escape(object param) => MainForm.Instance?.HideCommandPalette();
|
||||
|
||||
[RelayCommand]
|
||||
void Execute() => ExecuteInternal();
|
||||
|
||||
void OnLoaded(object sender, RoutedEventArgs e) => Keyboard.Focus(SearchControl.SearchTextBox);
|
||||
|
||||
public Theme Theme => Theme.Current!;
|
||||
|
||||
bool Filter(CommandPaletteItem item)
|
||||
{
|
||||
string filter = SearchControl.SearchTextBox.Text.ToLower();
|
||||
|
||||
if (item.Binding != null)
|
||||
{
|
||||
// TODO: CommandItem.Alias
|
||||
//if (item.CommandItem.Alias.ContainsEx(filter))
|
||||
// return true;
|
||||
|
||||
if (filter.Length == 1)
|
||||
return item.Binding.Input.ToLower()
|
||||
.Replace("ctrl+", "")
|
||||
.Replace("shift+", "")
|
||||
.Replace("alt+", "") == filter.ToLower();
|
||||
|
||||
if (item.Binding.Command.ToLower().Contains(filter))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (filter == "" || item.Text.ToLower().Contains(filter) ||
|
||||
item.SecondaryText.ToLower().Contains(filter))
|
||||
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void SelectFirst()
|
||||
{
|
||||
if (MainListView.Items.Count > 0)
|
||||
{
|
||||
MainListView.SelectedIndex = 0;
|
||||
MainListView.ScrollIntoView(MainListView.SelectedItem);
|
||||
}
|
||||
}
|
||||
|
||||
void ExecuteInternal()
|
||||
{
|
||||
if (MainListView.SelectedItem != null)
|
||||
{
|
||||
CommandPaletteItem? item = MainListView.SelectedItem as CommandPaletteItem;
|
||||
MainForm.Instance?.HideCommandPalette();
|
||||
item?.Action?.Invoke();
|
||||
//MainForm.Instance.Voodoo(); //TODO: Voodoo
|
||||
}
|
||||
}
|
||||
|
||||
public void SetItems(IEnumerable<CommandPaletteItem> items)
|
||||
{
|
||||
Items.Clear();
|
||||
|
||||
foreach (var i in items)
|
||||
Items.Add(i);
|
||||
}
|
||||
|
||||
public void AdjustHeight()
|
||||
{
|
||||
double actualHeight = SearchControl.ActualHeight + MainListView.ActualHeight + 5 + 16;
|
||||
int dpi = MainForm.GetDpi(MainForm.Instance!.Handle);
|
||||
MainForm.Instance.CommandPaletteHost.Height = (int)(actualHeight / 96.0 * dpi);
|
||||
}
|
||||
}
|
||||
27
src/MpvNet.Windows/WPF/Controls/HyperlinkEx.cs
Normal file
27
src/MpvNet.Windows/WPF/Controls/HyperlinkEx.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Navigation;
|
||||
|
||||
using MpvNet.Help;
|
||||
|
||||
// TODO: change namespace to MpvNet.Windows.WPF.Controls
|
||||
namespace MpvNet.Windows.WPF;
|
||||
|
||||
public class HyperlinkEx : Hyperlink
|
||||
{
|
||||
void HyperLinkEx_RequestNavigate(object sender, RequestNavigateEventArgs e)
|
||||
{
|
||||
ProcessHelp.ShellExecute(e.Uri.AbsoluteUri);
|
||||
}
|
||||
|
||||
public void SetURL(string? url)
|
||||
{
|
||||
if (string.IsNullOrEmpty(url))
|
||||
return;
|
||||
|
||||
NavigateUri = new Uri(url);
|
||||
RequestNavigate += HyperLinkEx_RequestNavigate;
|
||||
Inlines.Clear();
|
||||
Inlines.Add(url);
|
||||
}
|
||||
}
|
||||
74
src/MpvNet.Windows/WPF/Controls/OptionSettingControl.xaml
Normal file
74
src/MpvNet.Windows/WPF/Controls/OptionSettingControl.xaml
Normal file
@@ -0,0 +1,74 @@
|
||||
<UserControl
|
||||
x:Name="OptionSettingControl1"
|
||||
x:Class="MpvNet.Windows.WPF.OptionSettingControl"
|
||||
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:MpvNet.Windows.WPF"
|
||||
mc:Ignorable="d"
|
||||
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800">
|
||||
|
||||
<Grid Margin="20,0">
|
||||
<StackPanel>
|
||||
<TextBox
|
||||
x:Name="TitleTextBox"
|
||||
FontSize="24"
|
||||
Margin="0,10"
|
||||
BorderThickness="0"
|
||||
IsReadOnly="True"
|
||||
Foreground="{Binding Theme.Heading}"
|
||||
Background="{Binding Theme.Background}"
|
||||
/>
|
||||
|
||||
<ItemsControl x:Name="ItemsControl">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<WrapPanel Orientation="Vertical">
|
||||
<RadioButton
|
||||
x:Name="RadioButton"
|
||||
VerticalContentAlignment="Center"
|
||||
IsChecked="{Binding Checked}"
|
||||
GroupName="{Binding OptionSetting.Name}"
|
||||
Content="{Binding Text}"
|
||||
FontSize="16"
|
||||
FontWeight="Normal"
|
||||
VerticalAlignment="Top"
|
||||
Foreground="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Theme.Foreground}"
|
||||
/>
|
||||
|
||||
<TextBox
|
||||
x:Name="ItemHelpTextBox"
|
||||
TextWrapping="WrapWithOverflow"
|
||||
Text="{Binding Help}"
|
||||
Visibility="{Binding Visibility}"
|
||||
Margin="10,0,0,0" BorderThickness="0"
|
||||
IsReadOnly="True" Padding="7,0,0,0"
|
||||
MinHeight="0"
|
||||
Foreground="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Theme.Foreground2}"
|
||||
Background="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Theme.Background}"
|
||||
/>
|
||||
|
||||
</WrapPanel>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<TextBox
|
||||
x:Name="HelpTextBox"
|
||||
TextWrapping="WrapWithOverflow"
|
||||
BorderThickness="0"
|
||||
IsReadOnly="True"
|
||||
Margin="0,10,0,0"
|
||||
Foreground="{Binding Theme.Foreground}"
|
||||
Background="{Binding Theme.Background}"
|
||||
/>
|
||||
|
||||
<TextBlock x:Name="LinkTextBlock" Margin="0,10">
|
||||
<local:HyperlinkEx x:Name="Link"></local:HyperlinkEx>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
60
src/MpvNet.Windows/WPF/Controls/OptionSettingControl.xaml.cs
Normal file
60
src/MpvNet.Windows/WPF/Controls/OptionSettingControl.xaml.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
using MpvNet.Windows.UI;
|
||||
|
||||
namespace MpvNet.Windows.WPF;
|
||||
|
||||
public partial class OptionSettingControl : UserControl, ISettingControl
|
||||
{
|
||||
OptionSetting OptionSetting;
|
||||
|
||||
public OptionSettingControl(OptionSetting optionSetting)
|
||||
{
|
||||
OptionSetting = optionSetting;
|
||||
InitializeComponent();
|
||||
DataContext = this;
|
||||
TitleTextBox.Text = optionSetting.Name;
|
||||
|
||||
if (string.IsNullOrEmpty(optionSetting.Help))
|
||||
HelpTextBox.Visibility = Visibility.Collapsed;
|
||||
|
||||
HelpTextBox.Text = optionSetting.Help;
|
||||
ItemsControl.ItemsSource = optionSetting.Options;
|
||||
|
||||
if (string.IsNullOrEmpty(optionSetting.URL))
|
||||
LinkTextBlock.Visibility = Visibility.Collapsed;
|
||||
|
||||
Link.SetURL(optionSetting.URL);
|
||||
}
|
||||
|
||||
public Theme? Theme => Theme.Current;
|
||||
|
||||
public Setting Setting => OptionSetting;
|
||||
|
||||
public bool Contains(string searchString) => ContainsInternal(searchString.ToLower());
|
||||
|
||||
public bool ContainsInternal(string search)
|
||||
{
|
||||
if (TitleTextBox.Text.IndexOf(search, StringComparison.InvariantCultureIgnoreCase) > -1)
|
||||
return true;
|
||||
|
||||
if (HelpTextBox.Text.IndexOf(search, StringComparison.InvariantCultureIgnoreCase) > -1)
|
||||
return true;
|
||||
|
||||
foreach (var i in OptionSetting.Options)
|
||||
{
|
||||
if (i.Text?.IndexOf(search, StringComparison.InvariantCultureIgnoreCase) > -1)
|
||||
return true;
|
||||
|
||||
if (i.Help?.IndexOf(search, StringComparison.InvariantCultureIgnoreCase) > -1)
|
||||
return true;
|
||||
|
||||
if (i.Name?.IndexOf(search, StringComparison.InvariantCultureIgnoreCase) > -1)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
90
src/MpvNet.Windows/WPF/Controls/SearchControl.xaml
Normal file
90
src/MpvNet.Windows/WPF/Controls/SearchControl.xaml
Normal file
@@ -0,0 +1,90 @@
|
||||
<UserControl
|
||||
x:Class="MpvNet.Windows.WPF.Controls.SearchControl"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:controls="clr-namespace:MpvNet.Windows.WPF.Controls"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
>
|
||||
|
||||
<Grid Background="{Binding Theme.Background}">
|
||||
|
||||
<TextBlock
|
||||
Name="HintTextBlock"
|
||||
Padding="6,1"
|
||||
Text="Find a setting"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{Binding Theme.Foreground2}"
|
||||
Background="{Binding Theme.Background}"
|
||||
/>
|
||||
|
||||
<TextBox
|
||||
Name="SearchTextBox"
|
||||
Height="25"
|
||||
BorderThickness="2"
|
||||
Padding="2"
|
||||
Background="Transparent"
|
||||
Foreground="{Binding Theme.Foreground}"
|
||||
CaretBrush="{Binding Theme.Foreground}"
|
||||
GotFocus="SearchTextBox_GotFocus"
|
||||
PreviewMouseUp="SearchTextBox_PreviewMouseUp"
|
||||
Text="{Binding RelativeSource={RelativeSource FindAncestor,
|
||||
AncestorType={x:Type controls:SearchControl}},
|
||||
Path=Text, UpdateSourceTrigger=PropertyChanged}"
|
||||
/>
|
||||
|
||||
<Button
|
||||
Name="SearchClearButton"
|
||||
Background="Transparent"
|
||||
HorizontalAlignment="Right"
|
||||
FontFamily="Marlett"
|
||||
FontSize="10"
|
||||
Width="17"
|
||||
Height="17"
|
||||
Margin="2,0,4,0"
|
||||
Visibility="Hidden"
|
||||
Command="{Binding RelativeSource={RelativeSource FindAncestor,
|
||||
AncestorType={x:Type controls:SearchControl}}, Path=ClearCommand}"
|
||||
>r
|
||||
|
||||
<Button.Resources>
|
||||
<Style TargetType="{x:Type Border}">
|
||||
<Setter Property="CornerRadius" Value="3"/>
|
||||
</Style>
|
||||
</Button.Resources>
|
||||
|
||||
<Button.Style>
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="Background" Value="{Binding Theme.Background}"/>
|
||||
<Setter Property="Foreground" Value="{Binding Theme.Foreground2}"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border
|
||||
BorderThickness="1"
|
||||
BorderBrush="{TemplateBinding Foreground}"
|
||||
SnapsToDevicePixels="True"
|
||||
>
|
||||
|
||||
<ContentPresenter
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Foreground" Value="{Binding Theme.Heading}"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Button.Style>
|
||||
</Button>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
72
src/MpvNet.Windows/WPF/Controls/SearchControl.xaml.cs
Normal file
72
src/MpvNet.Windows/WPF/Controls/SearchControl.xaml.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
using MpvNet.Windows.UI;
|
||||
|
||||
namespace MpvNet.Windows.WPF.Controls;
|
||||
|
||||
public partial class SearchControl : UserControl
|
||||
{
|
||||
string? _hintText;
|
||||
bool _gotFocus;
|
||||
|
||||
public bool HideClearButton { get; set; }
|
||||
|
||||
public SearchControl() => InitializeComponent();
|
||||
|
||||
public Theme? Theme => Theme.Current;
|
||||
|
||||
public string HintText {
|
||||
get => _hintText ??= "";
|
||||
set {
|
||||
_hintText = value;
|
||||
UpdateControls();
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
void Clear()
|
||||
{
|
||||
Text = "";
|
||||
Keyboard.Focus(SearchTextBox);
|
||||
}
|
||||
|
||||
void UpdateControls()
|
||||
{
|
||||
HintTextBlock.Text = Text == "" ? HintText : "";
|
||||
|
||||
if (Text == "" || HideClearButton)
|
||||
SearchClearButton.Visibility = Visibility.Hidden;
|
||||
else
|
||||
SearchClearButton.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
public string Text
|
||||
{
|
||||
get => (string)GetValue(TextProperty);
|
||||
set => SetValue(TextProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty TextProperty =
|
||||
DependencyProperty.Register("Text", typeof(string),
|
||||
typeof(SearchControl), new PropertyMetadata(OnCustomerChangedCallBack));
|
||||
|
||||
static void OnCustomerChangedCallBack(
|
||||
DependencyObject sender, DependencyPropertyChangedEventArgs e) =>
|
||||
(sender as SearchControl)?.UpdateControls();
|
||||
|
||||
void SearchTextBox_GotFocus(object sender, RoutedEventArgs e) => _gotFocus = true;
|
||||
|
||||
void SearchTextBox_PreviewMouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (_gotFocus)
|
||||
{
|
||||
SearchTextBox?.SelectAll();
|
||||
_gotFocus = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
68
src/MpvNet.Windows/WPF/Controls/StringSettingControl.xaml
Normal file
68
src/MpvNet.Windows/WPF/Controls/StringSettingControl.xaml
Normal file
@@ -0,0 +1,68 @@
|
||||
<UserControl
|
||||
x:Name="StringSettingControl1"
|
||||
x:Class="MpvNet.Windows.WPF.StringSettingControl"
|
||||
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:MpvNet.Windows.WPF"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800">
|
||||
|
||||
<Grid Margin="20,0">
|
||||
<StackPanel>
|
||||
<TextBox
|
||||
x:Name="TitleTextBox"
|
||||
FontSize="24"
|
||||
Margin="0,10"
|
||||
BorderThickness="0"
|
||||
IsReadOnly="True"
|
||||
Foreground="{Binding Theme.Heading}"
|
||||
Background="{Binding Theme.Background}"
|
||||
/>
|
||||
|
||||
<Grid Margin="0,0,0,10">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBox
|
||||
x:Name="ValueTextBox"
|
||||
Text="{Binding Path=Text, ElementName=StringSettingControl1, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Width="150"
|
||||
FontSize="13"
|
||||
Padding="2"
|
||||
HorizontalAlignment="Left"
|
||||
Foreground="{Binding Theme.Foreground}"
|
||||
Background="{Binding Theme.Background}"
|
||||
CaretBrush="{Binding Theme.Foreground}"
|
||||
TextChanged="ValueTextBox_TextChanged"
|
||||
/>
|
||||
|
||||
<Button
|
||||
x:Name="Button"
|
||||
Width="25"
|
||||
Height="25"
|
||||
Grid.Column="1"
|
||||
Margin="5,0,0,0"
|
||||
Click="Button_Click"
|
||||
>...
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<TextBox
|
||||
x:Name="HelpTextBox"
|
||||
TextWrapping="WrapWithOverflow"
|
||||
BorderThickness="0"
|
||||
IsReadOnly="True"
|
||||
Foreground="{Binding Theme.Foreground}"
|
||||
Background="{Binding Theme.Background}"
|
||||
/>
|
||||
|
||||
<TextBlock x:Name="LinkTextBlock" Margin="0,10">
|
||||
<local:HyperlinkEx x:Name="Link"></local:HyperlinkEx>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
128
src/MpvNet.Windows/WPF/Controls/StringSettingControl.xaml.cs
Normal file
128
src/MpvNet.Windows/WPF/Controls/StringSettingControl.xaml.cs
Normal file
@@ -0,0 +1,128 @@
|
||||
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
|
||||
using Forms = System.Windows.Forms;
|
||||
|
||||
using MpvNet.Windows.UI;
|
||||
|
||||
namespace MpvNet.Windows.WPF;
|
||||
|
||||
public partial class StringSettingControl : UserControl, ISettingControl
|
||||
{
|
||||
StringSetting StringSetting;
|
||||
|
||||
public StringSettingControl(StringSetting stringSetting)
|
||||
{
|
||||
StringSetting = stringSetting;
|
||||
InitializeComponent();
|
||||
DataContext = this;
|
||||
TitleTextBox.Text = stringSetting.Name;
|
||||
HelpTextBox.Text = stringSetting.Help;
|
||||
ValueTextBox.Text = StringSetting.Value;
|
||||
|
||||
if (StringSetting.Width > 0)
|
||||
ValueTextBox.Width = StringSetting.Width;
|
||||
|
||||
if (StringSetting.Type != "folder" && StringSetting.Type != "color")
|
||||
Button.Visibility = Visibility.Hidden;
|
||||
|
||||
Link.SetURL(StringSetting.URL);
|
||||
|
||||
if (string.IsNullOrEmpty(stringSetting.URL))
|
||||
LinkTextBlock.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
public Theme? Theme => Theme.Current;
|
||||
|
||||
public bool Contains(string search)
|
||||
{
|
||||
if (TitleTextBox.Text.IndexOf(search, StringComparison.InvariantCultureIgnoreCase) > -1)
|
||||
return true;
|
||||
|
||||
if (HelpTextBox.Text.IndexOf(search, StringComparison.InvariantCultureIgnoreCase) > -1)
|
||||
return true;
|
||||
|
||||
if (ValueTextBox.Text.IndexOf(search, StringComparison.InvariantCultureIgnoreCase) > -1)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public Setting Setting => StringSetting;
|
||||
|
||||
public string? Text
|
||||
{
|
||||
get => StringSetting.Value;
|
||||
set => StringSetting.Value = value;
|
||||
}
|
||||
|
||||
void Button_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
switch (StringSetting.Type)
|
||||
{
|
||||
case "folder":
|
||||
{
|
||||
var dialog = new Forms.FolderBrowserDialog { InitialDirectory = ValueTextBox.Text };
|
||||
|
||||
if (dialog.ShowDialog() == Forms.DialogResult.OK)
|
||||
ValueTextBox.Text = dialog.SelectedPath;
|
||||
}
|
||||
break;
|
||||
case "color":
|
||||
using (var dialog = new Forms.ColorDialog())
|
||||
{
|
||||
dialog.FullOpen = true;
|
||||
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(ValueTextBox.Text))
|
||||
{
|
||||
Color col = GetColor(ValueTextBox.Text);
|
||||
dialog.Color = System.Drawing.Color.FromArgb(col.A, col.R, col.G, col.B);
|
||||
}
|
||||
} catch {}
|
||||
|
||||
if (dialog.ShowDialog() == Forms.DialogResult.OK)
|
||||
ValueTextBox.Text = "#" + dialog.Color.ToArgb().ToString("X8");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ValueTextBox_TextChanged(object sender, TextChangedEventArgs e) => Update();
|
||||
|
||||
Color GetColor(string value)
|
||||
{
|
||||
if (value.Contains('/'))
|
||||
{
|
||||
string[] a = value.Split('/');
|
||||
|
||||
if (a.Length == 3)
|
||||
return Color.FromRgb(ToByte(a[0]), ToByte(a[1]), ToByte(a[2]));
|
||||
else if (a.Length == 4)
|
||||
return Color.FromArgb(ToByte(a[3]), ToByte(a[0]), ToByte(a[1]), ToByte(a[2]));
|
||||
}
|
||||
|
||||
return (Color)ColorConverter.ConvertFromString(value);
|
||||
|
||||
byte ToByte(string val) => Convert.ToByte(Convert.ToSingle(val, CultureInfo.InvariantCulture) * 255);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (StringSetting.Type == "color")
|
||||
{
|
||||
Color color = Colors.Transparent;
|
||||
|
||||
if (ValueTextBox.Text != "")
|
||||
try {
|
||||
color = GetColor(ValueTextBox.Text);
|
||||
} catch {}
|
||||
|
||||
ValueTextBox.Background = new SolidColorBrush(color);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Tools.Converter;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class BorderElement
|
||||
{
|
||||
public static readonly DependencyProperty CornerRadiusProperty = DependencyProperty.RegisterAttached(
|
||||
"CornerRadius", typeof(CornerRadius), typeof(BorderElement), new FrameworkPropertyMetadata(default(CornerRadius), FrameworkPropertyMetadataOptions.Inherits));
|
||||
|
||||
public static void SetCornerRadius(DependencyObject element, CornerRadius value) => element.SetValue(CornerRadiusProperty, value);
|
||||
|
||||
public static CornerRadius GetCornerRadius(DependencyObject element) => (CornerRadius) element.GetValue(CornerRadiusProperty);
|
||||
|
||||
public static readonly DependencyProperty CircularProperty = DependencyProperty.RegisterAttached(
|
||||
"Circular", typeof(bool), typeof(BorderElement), new PropertyMetadata(ValueBoxes.FalseBox, OnCircularChanged));
|
||||
|
||||
private static void OnCircularChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is Border border)
|
||||
{
|
||||
if ((bool) e.NewValue)
|
||||
{
|
||||
var binding = new MultiBinding
|
||||
{
|
||||
Converter = new BorderCircularConverter()
|
||||
};
|
||||
binding.Bindings.Add(new Binding(FrameworkElement.ActualWidthProperty.Name) { Source = border });
|
||||
binding.Bindings.Add(new Binding(FrameworkElement.ActualHeightProperty.Name) { Source = border });
|
||||
border.SetBinding(Border.CornerRadiusProperty, binding);
|
||||
}
|
||||
else
|
||||
{
|
||||
BindingOperations.ClearBinding(border, FrameworkElement.ActualWidthProperty);
|
||||
BindingOperations.ClearBinding(border, FrameworkElement.ActualHeightProperty);
|
||||
BindingOperations.ClearBinding(border, Border.CornerRadiusProperty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetCircular(DependencyObject element, bool value)
|
||||
=> element.SetValue(CircularProperty, ValueBoxes.BooleanBox(value));
|
||||
|
||||
public static bool GetCircular(DependencyObject element)
|
||||
=> (bool) element.GetValue(CircularProperty);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class IconElement
|
||||
{
|
||||
public static readonly DependencyProperty GeometryProperty = DependencyProperty.RegisterAttached(
|
||||
"Geometry", typeof(Geometry), typeof(IconElement), new PropertyMetadata(default(Geometry)));
|
||||
|
||||
public static void SetGeometry(DependencyObject element, Geometry value)
|
||||
=> element.SetValue(GeometryProperty, value);
|
||||
|
||||
public static Geometry GetGeometry(DependencyObject element)
|
||||
=> (Geometry) element.GetValue(GeometryProperty);
|
||||
|
||||
public static readonly DependencyProperty WidthProperty = DependencyProperty.RegisterAttached(
|
||||
"Width", typeof(double), typeof(IconElement), new PropertyMetadata(double.NaN));
|
||||
|
||||
public static void SetWidth(DependencyObject element, double value)
|
||||
=> element.SetValue(WidthProperty, value);
|
||||
|
||||
public static double GetWidth(DependencyObject element)
|
||||
=> (double) element.GetValue(WidthProperty);
|
||||
|
||||
public static readonly DependencyProperty HeightProperty = DependencyProperty.RegisterAttached(
|
||||
"Height", typeof(double), typeof(IconElement), new PropertyMetadata(double.NaN));
|
||||
|
||||
public static void SetHeight(DependencyObject element, double value)
|
||||
=> element.SetValue(HeightProperty, value);
|
||||
|
||||
public static double GetHeight(DependencyObject element)
|
||||
=> (double) element.GetValue(HeightProperty);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
|
||||
using HandyControl.Tools;
|
||||
using HandyControl.Tools.Interop;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class MenuTopLineAttach
|
||||
{
|
||||
public static readonly DependencyProperty PopupProperty = DependencyProperty.RegisterAttached(
|
||||
"Popup", typeof(Popup), typeof(MenuTopLineAttach), new PropertyMetadata(default(Popup), OnPopupChanged));
|
||||
|
||||
private static void OnPopupChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var topLine = (FrameworkElement)d;
|
||||
|
||||
if (e.NewValue is Popup)
|
||||
{
|
||||
Popup popup = e.NewValue as Popup;
|
||||
MenuItem menuItem = popup.TemplatedParent as MenuItem;
|
||||
SetTopLine(menuItem, topLine);
|
||||
menuItem.Loaded += MenuItem_Loaded;
|
||||
}
|
||||
}
|
||||
|
||||
private static void MenuItem_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var menuItem = (FrameworkElement)sender;
|
||||
menuItem.Unloaded += MenuItem_Unloaded;
|
||||
var topLine = GetTopLine(menuItem);
|
||||
var popup = GetPopup(topLine);
|
||||
if (popup != null)
|
||||
{
|
||||
popup.Opened += Popup_Opened;
|
||||
}
|
||||
}
|
||||
|
||||
private static void MenuItem_Unloaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var menuItem = (FrameworkElement)sender;
|
||||
menuItem.Unloaded -= MenuItem_Unloaded;
|
||||
var topLine = GetTopLine(menuItem);
|
||||
var popup = GetPopup(topLine);
|
||||
if (popup != null)
|
||||
{
|
||||
popup.Opened -= Popup_Opened;
|
||||
}
|
||||
}
|
||||
|
||||
private static void Popup_Opened(object sender, EventArgs e)
|
||||
{
|
||||
var popup = (Popup)sender;
|
||||
if (popup.TemplatedParent is MenuItem menuItem)
|
||||
{
|
||||
var topLine = GetTopLine(menuItem);
|
||||
if (topLine == null) return;
|
||||
|
||||
topLine.HorizontalAlignment = HorizontalAlignment.Left;
|
||||
topLine.Width = menuItem.ActualWidth;
|
||||
topLine.Margin = new Thickness();
|
||||
|
||||
var positionLeftTop = menuItem.PointToScreen(new Point());
|
||||
var positionRightBottom = menuItem.PointToScreen(new Point(menuItem.ActualWidth, menuItem.ActualHeight));
|
||||
ScreenHelper.FindMonitorRectsFromPoint(InteropMethods.GetCursorPos(), out Rect monitorRect, out var workAreaRect);
|
||||
var panel = VisualHelper.GetParent<Panel>(topLine);
|
||||
|
||||
if (positionLeftTop.X < 0)
|
||||
{
|
||||
|
||||
topLine.Margin = new Thickness(positionLeftTop.X - panel.Margin.Left, 0, 0, 0);
|
||||
}
|
||||
else if (positionLeftTop.X + panel.ActualWidth > workAreaRect.Right)
|
||||
{
|
||||
var overflowWidth = positionRightBottom.X - workAreaRect.Right;
|
||||
if (overflowWidth > 0)
|
||||
{
|
||||
topLine.Width -= overflowWidth + panel.Margin.Right;
|
||||
}
|
||||
topLine.HorizontalAlignment = HorizontalAlignment.Left;
|
||||
topLine.Margin = new Thickness(positionLeftTop.X + panel.ActualWidth - workAreaRect.Right + panel.Margin.Right, 0, 0, 0);
|
||||
}
|
||||
|
||||
if (positionRightBottom.Y > workAreaRect.Bottom)
|
||||
{
|
||||
topLine.Width = 0;
|
||||
topLine.HorizontalAlignment = HorizontalAlignment.Stretch;
|
||||
topLine.Margin = new Thickness();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetPopup(DependencyObject element, Popup value)
|
||||
=> element.SetValue(PopupProperty, value);
|
||||
|
||||
public static Popup GetPopup(DependencyObject element)
|
||||
=> (Popup)element.GetValue(PopupProperty);
|
||||
|
||||
internal static readonly DependencyProperty TopLineProperty = DependencyProperty.RegisterAttached(
|
||||
"TopLine", typeof(FrameworkElement), typeof(MenuTopLineAttach), new PropertyMetadata(default(FrameworkElement)));
|
||||
|
||||
internal static void SetTopLine(DependencyObject element, FrameworkElement value)
|
||||
=> element.SetValue(TopLineProperty, value);
|
||||
|
||||
internal static FrameworkElement GetTopLine(DependencyObject element)
|
||||
=> (FrameworkElement)element.GetValue(TopLineProperty);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
|
||||
using System.Windows;
|
||||
|
||||
using HandyControl.Data;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class ScrollViewerAttach
|
||||
{
|
||||
public static readonly DependencyProperty AutoHideProperty = DependencyProperty.RegisterAttached(
|
||||
"AutoHide", typeof(bool), typeof(ScrollViewerAttach), new FrameworkPropertyMetadata(ValueBoxes.TrueBox, FrameworkPropertyMetadataOptions.Inherits));
|
||||
|
||||
public static void SetAutoHide(DependencyObject element, bool value)
|
||||
=> element.SetValue(AutoHideProperty, value);
|
||||
|
||||
public static bool GetAutoHide(DependencyObject element)
|
||||
=> (bool) element.GetValue(AutoHideProperty);
|
||||
}
|
||||
}
|
||||
192
src/MpvNet.Windows/WPF/HandyControl/Controls/ScrollViewer.cs
Normal file
192
src/MpvNet.Windows/WPF/HandyControl/Controls/ScrollViewer.cs
Normal file
@@ -0,0 +1,192 @@
|
||||
|
||||
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Tools;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class ScrollViewer : System.Windows.Controls.ScrollViewer
|
||||
{
|
||||
private double _totalVerticalOffset;
|
||||
|
||||
private double _totalHorizontalOffset;
|
||||
|
||||
private bool _isRunning;
|
||||
|
||||
public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(
|
||||
"Orientation", typeof(Orientation), typeof(ScrollViewer), new PropertyMetadata(Orientation.Vertical));
|
||||
|
||||
public Orientation Orientation
|
||||
{
|
||||
get => (Orientation) GetValue(OrientationProperty);
|
||||
set => SetValue(OrientationProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty CanMouseWheelProperty = DependencyProperty.Register(
|
||||
"CanMouseWheel", typeof(bool), typeof(ScrollViewer), new PropertyMetadata(ValueBoxes.TrueBox));
|
||||
|
||||
public bool CanMouseWheel
|
||||
{
|
||||
get => (bool) GetValue(CanMouseWheelProperty);
|
||||
set => SetValue(CanMouseWheelProperty, ValueBoxes.BooleanBox(value));
|
||||
}
|
||||
|
||||
protected override void OnMouseWheel(MouseWheelEventArgs e)
|
||||
{
|
||||
if (!CanMouseWheel) return;
|
||||
|
||||
if (!IsInertiaEnabled)
|
||||
{
|
||||
if (Orientation == Orientation.Vertical)
|
||||
{
|
||||
base.OnMouseWheel(e);
|
||||
}
|
||||
else
|
||||
{
|
||||
_totalHorizontalOffset = HorizontalOffset;
|
||||
CurrentHorizontalOffset = HorizontalOffset;
|
||||
_totalHorizontalOffset = Math.Min(Math.Max(0, _totalHorizontalOffset - e.Delta), ScrollableWidth);
|
||||
CurrentHorizontalOffset = _totalHorizontalOffset;
|
||||
}
|
||||
return;
|
||||
}
|
||||
e.Handled = true;
|
||||
|
||||
if (Orientation == Orientation.Vertical)
|
||||
{
|
||||
if (!_isRunning)
|
||||
{
|
||||
_totalVerticalOffset = VerticalOffset;
|
||||
CurrentVerticalOffset = VerticalOffset;
|
||||
}
|
||||
_totalVerticalOffset = Math.Min(Math.Max(0, _totalVerticalOffset - e.Delta), ScrollableHeight);
|
||||
ScrollToVerticalOffsetWithAnimation(_totalVerticalOffset);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!_isRunning)
|
||||
{
|
||||
_totalHorizontalOffset = HorizontalOffset;
|
||||
CurrentHorizontalOffset = HorizontalOffset;
|
||||
}
|
||||
_totalHorizontalOffset = Math.Min(Math.Max(0, _totalHorizontalOffset - e.Delta), ScrollableWidth);
|
||||
ScrollToHorizontalOffsetWithAnimation(_totalHorizontalOffset);
|
||||
}
|
||||
}
|
||||
|
||||
internal void ScrollToTopInternal(double milliseconds = 500)
|
||||
{
|
||||
if (!_isRunning)
|
||||
{
|
||||
_totalVerticalOffset = VerticalOffset;
|
||||
CurrentVerticalOffset = VerticalOffset;
|
||||
}
|
||||
ScrollToVerticalOffsetWithAnimation(0, milliseconds);
|
||||
}
|
||||
|
||||
public void ScrollToVerticalOffsetWithAnimation(double offset, double milliseconds = 500)
|
||||
{
|
||||
var animation = AnimationHelper.CreateAnimation(offset, milliseconds);
|
||||
animation.EasingFunction = new CubicEase
|
||||
{
|
||||
EasingMode = EasingMode.EaseOut
|
||||
};
|
||||
animation.FillBehavior = FillBehavior.Stop;
|
||||
animation.Completed += (s, e1) =>
|
||||
{
|
||||
CurrentVerticalOffset = offset;
|
||||
_isRunning = false;
|
||||
};
|
||||
_isRunning = true;
|
||||
|
||||
BeginAnimation(CurrentVerticalOffsetProperty, animation, HandoffBehavior.Compose);
|
||||
}
|
||||
|
||||
public void ScrollToHorizontalOffsetWithAnimation(double offset, double milliseconds = 500)
|
||||
{
|
||||
var animation = AnimationHelper.CreateAnimation(offset, milliseconds);
|
||||
animation.EasingFunction = new CubicEase
|
||||
{
|
||||
EasingMode = EasingMode.EaseOut
|
||||
};
|
||||
animation.FillBehavior = FillBehavior.Stop;
|
||||
animation.Completed += (s, e1) =>
|
||||
{
|
||||
CurrentHorizontalOffset = offset;
|
||||
_isRunning = false;
|
||||
};
|
||||
_isRunning = true;
|
||||
|
||||
BeginAnimation(CurrentHorizontalOffsetProperty, animation, HandoffBehavior.Compose);
|
||||
}
|
||||
|
||||
protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters) =>
|
||||
IsPenetrating ? null : base.HitTestCore(hitTestParameters);
|
||||
|
||||
public static readonly DependencyProperty IsInertiaEnabledProperty = DependencyProperty.RegisterAttached(
|
||||
"IsInertiaEnabled", typeof(bool), typeof(ScrollViewer), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
public static void SetIsInertiaEnabled(DependencyObject element, bool value) => element.SetValue(IsInertiaEnabledProperty, ValueBoxes.BooleanBox(value));
|
||||
|
||||
public static bool GetIsInertiaEnabled(DependencyObject element) => (bool) element.GetValue(IsInertiaEnabledProperty);
|
||||
|
||||
public bool IsInertiaEnabled
|
||||
{
|
||||
get => (bool) GetValue(IsInertiaEnabledProperty);
|
||||
set => SetValue(IsInertiaEnabledProperty, ValueBoxes.BooleanBox(value));
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IsPenetratingProperty = DependencyProperty.RegisterAttached(
|
||||
"IsPenetrating", typeof(bool), typeof(ScrollViewer), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
public bool IsPenetrating
|
||||
{
|
||||
get => (bool) GetValue(IsPenetratingProperty);
|
||||
set => SetValue(IsPenetratingProperty, ValueBoxes.BooleanBox(value));
|
||||
}
|
||||
|
||||
public static void SetIsPenetrating(DependencyObject element, bool value) => element.SetValue(IsPenetratingProperty, ValueBoxes.BooleanBox(value));
|
||||
|
||||
public static bool GetIsPenetrating(DependencyObject element) => (bool) element.GetValue(IsPenetratingProperty);
|
||||
|
||||
internal static readonly DependencyProperty CurrentVerticalOffsetProperty = DependencyProperty.Register(
|
||||
"CurrentVerticalOffset", typeof(double), typeof(ScrollViewer), new PropertyMetadata(ValueBoxes.Double0Box, OnCurrentVerticalOffsetChanged));
|
||||
|
||||
private static void OnCurrentVerticalOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is ScrollViewer ctl && e.NewValue is double v)
|
||||
{
|
||||
ctl.ScrollToVerticalOffset(v);
|
||||
}
|
||||
}
|
||||
|
||||
internal double CurrentVerticalOffset
|
||||
{
|
||||
get => (double) GetValue(CurrentVerticalOffsetProperty);
|
||||
set => SetValue(CurrentVerticalOffsetProperty, value);
|
||||
}
|
||||
|
||||
internal static readonly DependencyProperty CurrentHorizontalOffsetProperty = DependencyProperty.Register(
|
||||
"CurrentHorizontalOffset", typeof(double), typeof(ScrollViewer), new PropertyMetadata(ValueBoxes.Double0Box, OnCurrentHorizontalOffsetChanged));
|
||||
|
||||
private static void OnCurrentHorizontalOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is ScrollViewer ctl && e.NewValue is double v)
|
||||
{
|
||||
ctl.ScrollToHorizontalOffset(v);
|
||||
}
|
||||
}
|
||||
|
||||
internal double CurrentHorizontalOffset
|
||||
{
|
||||
get => (double) GetValue(CurrentHorizontalOffsetProperty);
|
||||
set => SetValue(CurrentHorizontalOffsetProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
37
src/MpvNet.Windows/WPF/HandyControl/Controls/SimplePanel.cs
Normal file
37
src/MpvNet.Windows/WPF/HandyControl/Controls/SimplePanel.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
|
||||
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class SimplePanel : Panel
|
||||
{
|
||||
protected override Size MeasureOverride(Size constraint)
|
||||
{
|
||||
var maxSize = new Size();
|
||||
|
||||
foreach (UIElement child in InternalChildren)
|
||||
{
|
||||
if (child != null)
|
||||
{
|
||||
child.Measure(constraint);
|
||||
maxSize.Width = Math.Max(maxSize.Width, child.DesiredSize.Width);
|
||||
maxSize.Height = Math.Max(maxSize.Height, child.DesiredSize.Height);
|
||||
}
|
||||
}
|
||||
|
||||
return maxSize;
|
||||
}
|
||||
|
||||
protected override Size ArrangeOverride(Size arrangeSize)
|
||||
{
|
||||
foreach (UIElement child in InternalChildren)
|
||||
{
|
||||
child?.Arrange(new Rect(arrangeSize));
|
||||
}
|
||||
|
||||
return arrangeSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
24
src/MpvNet.Windows/WPF/HandyControl/Data/ValueBoxes.cs
Normal file
24
src/MpvNet.Windows/WPF/HandyControl/Data/ValueBoxes.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
|
||||
namespace HandyControl.Data
|
||||
{
|
||||
internal static class ValueBoxes
|
||||
{
|
||||
internal static object TrueBox = true;
|
||||
internal static object FalseBox = false;
|
||||
internal static object Double0Box = .0;
|
||||
internal static object Double01Box = .1;
|
||||
internal static object Double1Box = 1.0;
|
||||
internal static object Double10Box = 10.0;
|
||||
internal static object Double20Box = 20.0;
|
||||
internal static object Double100Box = 100.0;
|
||||
internal static object Double200Box = 200.0;
|
||||
internal static object Double300Box = 300.0;
|
||||
internal static object DoubleNeg1Box = -1.0;
|
||||
internal static object Int0Box = 0;
|
||||
internal static object Int1Box = 1;
|
||||
internal static object Int2Box = 2;
|
||||
internal static object Int5Box = 5;
|
||||
internal static object Int99Box = 99;
|
||||
internal static object BooleanBox(bool value) => value ? TrueBox : FalseBox;
|
||||
}
|
||||
}
|
||||
80
src/MpvNet.Windows/WPF/HandyControl/Tools/AnimationHelper.cs
Normal file
80
src/MpvNet.Windows/WPF/HandyControl/Tools/AnimationHelper.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
|
||||
using HandyControl.Tools.Extension;
|
||||
|
||||
namespace HandyControl.Tools
|
||||
{
|
||||
public class AnimationHelper
|
||||
{
|
||||
public static ThicknessAnimation CreateAnimation(Thickness thickness = default, double milliseconds = 200)
|
||||
{
|
||||
return new ThicknessAnimation(thickness, new Duration(TimeSpan.FromMilliseconds(milliseconds)))
|
||||
{
|
||||
EasingFunction = new PowerEase { EasingMode = EasingMode.EaseInOut }
|
||||
};
|
||||
}
|
||||
|
||||
public static DoubleAnimation CreateAnimation(double toValue, double milliseconds = 200)
|
||||
{
|
||||
return new DoubleAnimation(toValue, new Duration(TimeSpan.FromMilliseconds(milliseconds)))
|
||||
{
|
||||
EasingFunction = new PowerEase { EasingMode = EasingMode.EaseInOut }
|
||||
};
|
||||
}
|
||||
|
||||
internal static void DecomposeGeometryStr(string geometryStr, out double[] arr)
|
||||
{
|
||||
var collection = Regex.Matches(geometryStr, RegexPatterns.DigitsPattern);
|
||||
arr = new double[collection.Count];
|
||||
for (var i = 0; i < collection.Count; i++)
|
||||
{
|
||||
arr[i] = collection[i].Value.Value<double>();
|
||||
}
|
||||
}
|
||||
|
||||
internal static Geometry ComposeGeometry(string[] strings, double[] arr)
|
||||
{
|
||||
var builder = new StringBuilder(strings[0]);
|
||||
for (var i = 0; i < arr.Length; i++)
|
||||
{
|
||||
var s = strings[i + 1];
|
||||
var n = arr[i];
|
||||
if (!double.IsNaN(n))
|
||||
{
|
||||
builder.Append(n).Append(s);
|
||||
}
|
||||
}
|
||||
|
||||
return Geometry.Parse(builder.ToString());
|
||||
}
|
||||
|
||||
internal static Geometry InterpolateGeometry(double[] from, double[] to, double progress, string[] strings)
|
||||
{
|
||||
var accumulated = new double[to.Length];
|
||||
for (var i = 0; i < to.Length; i++)
|
||||
{
|
||||
var fromValue = from[i];
|
||||
accumulated[i] = fromValue + (to[i] - fromValue) * progress;
|
||||
}
|
||||
|
||||
return ComposeGeometry(strings, accumulated);
|
||||
}
|
||||
|
||||
internal static double[] InterpolateGeometryValue(double[] from, double[] to, double progress)
|
||||
{
|
||||
var accumulated = new double[to.Length];
|
||||
for (var i = 0; i < to.Length; i++)
|
||||
{
|
||||
var fromValue = from[i];
|
||||
accumulated[i] = fromValue + (to[i] - fromValue) * progress;
|
||||
}
|
||||
|
||||
return accumulated;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
|
||||
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace HandyControl.Tools.Converter
|
||||
{
|
||||
public class BorderCircularConverter : IMultiValueConverter
|
||||
{
|
||||
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (values.Length == 2 && values[0] is double width && values[1] is double height)
|
||||
{
|
||||
if (width < double.Epsilon || height < double.Epsilon)
|
||||
{
|
||||
return new CornerRadius();
|
||||
}
|
||||
|
||||
var min = Math.Min(width, height);
|
||||
return new CornerRadius(min / 2);
|
||||
}
|
||||
|
||||
return DependencyProperty.UnsetValue;
|
||||
}
|
||||
|
||||
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
|
||||
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace HandyControl.Tools.Extension
|
||||
{
|
||||
public static class StringExtension
|
||||
{
|
||||
public static T Value<T>(this string input)
|
||||
{
|
||||
try
|
||||
{
|
||||
return (T) TypeDescriptor.GetConverter(typeof(T)).ConvertFromString(input);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
public static object Value(this string input, Type type)
|
||||
{
|
||||
try
|
||||
{
|
||||
return TypeDescriptor.GetConverter(type).ConvertFromString(input);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
|
||||
using HandyControl.Tools.Interop;
|
||||
|
||||
namespace HandyControl.Tools
|
||||
{
|
||||
internal class ScreenHelper
|
||||
{
|
||||
internal static void FindMaximumSingleMonitorRectangle(Rect windowRect, out Rect screenSubRect, out Rect monitorRect)
|
||||
{
|
||||
var windowRect2 = new InteropValues.RECT(windowRect);
|
||||
FindMaximumSingleMonitorRectangle(windowRect2, out var rect, out var rect2);
|
||||
screenSubRect = new Rect(rect.Position, rect.Size);
|
||||
monitorRect = new Rect(rect2.Position, rect2.Size);
|
||||
}
|
||||
|
||||
private static void FindMaximumSingleMonitorRectangle(InteropValues.RECT windowRect, out InteropValues.RECT screenSubRect, out InteropValues.RECT monitorRect)
|
||||
{
|
||||
var rects = new List<InteropValues.RECT>();
|
||||
InteropMethods.EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero,
|
||||
delegate (IntPtr hMonitor, IntPtr hdcMonitor, ref InteropValues.RECT rect, IntPtr lpData)
|
||||
{
|
||||
var monitorInfo = default(InteropValues.MONITORINFO);
|
||||
monitorInfo.cbSize = (uint) Marshal.SizeOf(typeof(InteropValues.MONITORINFO));
|
||||
InteropMethods.GetMonitorInfo(hMonitor, ref monitorInfo);
|
||||
rects.Add(monitorInfo.rcWork);
|
||||
return true;
|
||||
}, IntPtr.Zero);
|
||||
|
||||
var num = 0L;
|
||||
|
||||
screenSubRect = new InteropValues.RECT
|
||||
{
|
||||
Left = 0,
|
||||
Right = 0,
|
||||
Top = 0,
|
||||
Bottom = 0
|
||||
};
|
||||
|
||||
monitorRect = new InteropValues.RECT
|
||||
{
|
||||
Left = 0,
|
||||
Right = 0,
|
||||
Top = 0,
|
||||
Bottom = 0
|
||||
};
|
||||
|
||||
foreach (var current in rects)
|
||||
{
|
||||
var rect = current;
|
||||
InteropMethods.IntersectRect(out var rECT2, ref rect, ref windowRect);
|
||||
var num2 = (long) (rECT2.Width * rECT2.Height);
|
||||
if (num2 > num)
|
||||
{
|
||||
screenSubRect = rECT2;
|
||||
monitorRect = current;
|
||||
num = num2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void FindMonitorRectsFromPoint(Point point, out Rect monitorRect, out Rect workAreaRect)
|
||||
{
|
||||
var intPtr = InteropMethods.MonitorFromPoint(new InteropValues.POINT
|
||||
{
|
||||
X = (int) point.X,
|
||||
Y = (int) point.Y
|
||||
}, 2);
|
||||
|
||||
monitorRect = new Rect(0.0, 0.0, 0.0, 0.0);
|
||||
workAreaRect = new Rect(0.0, 0.0, 0.0, 0.0);
|
||||
|
||||
if (intPtr != IntPtr.Zero)
|
||||
{
|
||||
InteropValues.MONITORINFO monitorInfo = default;
|
||||
monitorInfo.cbSize = (uint) Marshal.SizeOf(typeof(InteropValues.MONITORINFO));
|
||||
InteropMethods.GetMonitorInfo(intPtr, ref monitorInfo);
|
||||
monitorRect = new Rect(monitorInfo.rcMonitor.Position, monitorInfo.rcMonitor.Size);
|
||||
workAreaRect = new Rect(monitorInfo.rcWork.Position, monitorInfo.rcWork.Size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
|
||||
using System.Windows;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using HandyControl.Tools.Interop;
|
||||
|
||||
namespace HandyControl.Tools
|
||||
{
|
||||
public static class VisualHelper
|
||||
{
|
||||
internal static VisualStateGroup TryGetVisualStateGroup(DependencyObject d, string groupName)
|
||||
{
|
||||
var root = GetImplementationRoot(d);
|
||||
if (root == null) return null;
|
||||
|
||||
return VisualStateManager
|
||||
.GetVisualStateGroups(root)?
|
||||
.OfType<VisualStateGroup>()
|
||||
.FirstOrDefault(group => string.CompareOrdinal(groupName, group.Name) == 0);
|
||||
}
|
||||
|
||||
internal static FrameworkElement GetImplementationRoot(DependencyObject d) =>
|
||||
1 == VisualTreeHelper.GetChildrenCount(d)
|
||||
? VisualTreeHelper.GetChild(d, 0) as FrameworkElement
|
||||
: null;
|
||||
|
||||
public static T GetChild<T>(DependencyObject d) where T : DependencyObject
|
||||
{
|
||||
if (d == null) return default;
|
||||
if (d is T t) return t;
|
||||
|
||||
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(d); i++)
|
||||
{
|
||||
var child = VisualTreeHelper.GetChild(d, i);
|
||||
|
||||
var result = GetChild<T>(child);
|
||||
if (result != null) return result;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public static T GetParent<T>(DependencyObject d) where T : DependencyObject
|
||||
{
|
||||
if (d == null)
|
||||
return default;
|
||||
|
||||
if (d is T)
|
||||
return d as T;
|
||||
|
||||
if (d is Window)
|
||||
return null;
|
||||
|
||||
return GetParent<T>(VisualTreeHelper.GetParent(d));
|
||||
}
|
||||
|
||||
public static IntPtr GetHandle(this Visual visual) => (PresentationSource.FromVisual(visual) as HwndSource)?.Handle ?? IntPtr.Zero;
|
||||
|
||||
internal static void HitTestVisibleElements(Visual visual, HitTestResultCallback resultCallback, HitTestParameters parameters) =>
|
||||
VisualTreeHelper.HitTest(visual, ExcludeNonVisualElements, resultCallback, parameters);
|
||||
|
||||
private static HitTestFilterBehavior ExcludeNonVisualElements(DependencyObject potentialHitTestTarget)
|
||||
{
|
||||
if (!(potentialHitTestTarget is Visual)) return HitTestFilterBehavior.ContinueSkipSelfAndChildren;
|
||||
|
||||
if (!(potentialHitTestTarget is UIElement uIElement) || uIElement.IsVisible && uIElement.IsEnabled)
|
||||
return HitTestFilterBehavior.Continue;
|
||||
|
||||
return HitTestFilterBehavior.ContinueSkipSelfAndChildren;
|
||||
}
|
||||
|
||||
internal static bool ModifyStyle(IntPtr hWnd, int styleToRemove, int styleToAdd)
|
||||
{
|
||||
var windowLong = InteropMethods.GetWindowLong(hWnd, InteropValues.GWL.STYLE);
|
||||
var num = (windowLong & ~styleToRemove) | styleToAdd;
|
||||
if (num == windowLong) return false;
|
||||
InteropMethods.SetWindowLong(hWnd, InteropValues.GWL.STYLE, num);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
|
||||
using System.Runtime.ConstrainedExecution;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
|
||||
namespace HandyControl.Tools.Interop
|
||||
{
|
||||
internal sealed class BitmapHandle : WpfSafeHandle
|
||||
{
|
||||
[SecurityCritical]
|
||||
private BitmapHandle() : this(true)
|
||||
{
|
||||
}
|
||||
|
||||
[SecurityCritical]
|
||||
private BitmapHandle(bool ownsHandle) : base(ownsHandle, CommonHandles.GDI)
|
||||
{
|
||||
}
|
||||
|
||||
[SecurityCritical]
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
return InteropMethods.DeleteObject(handle);
|
||||
}
|
||||
|
||||
[SecurityCritical]
|
||||
internal HandleRef MakeHandleRef(object obj)
|
||||
{
|
||||
return new HandleRef(obj, handle);
|
||||
}
|
||||
|
||||
[SecurityCritical]
|
||||
internal static BitmapHandle CreateFromHandle(IntPtr hbitmap, bool ownsHandle = true)
|
||||
{
|
||||
return new BitmapHandle(ownsHandle)
|
||||
{
|
||||
handle = hbitmap,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
namespace HandyControl.Tools.Interop
|
||||
{
|
||||
internal static class CommonHandles
|
||||
{
|
||||
public static readonly int Icon = HandleCollector.RegisterType(nameof(Icon), 20, 500);
|
||||
|
||||
public static readonly int HDC = HandleCollector.RegisterType(nameof(HDC), 100, 2);
|
||||
|
||||
public static readonly int GDI = HandleCollector.RegisterType(nameof(GDI), 50, 500);
|
||||
|
||||
public static readonly int Kernel = HandleCollector.RegisterType(nameof(Kernel), 0, 1000);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
// reference from https://referencesource.microsoft.com/#WindowsBase/Shared/MS/Win32/HandleCollector.cs,d0f99220d8e1b708
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace HandyControl.Tools.Interop
|
||||
{
|
||||
internal static class HandleCollector
|
||||
{
|
||||
private static HandleType[] HandleTypes;
|
||||
private static int HandleTypeCount;
|
||||
|
||||
private static readonly object HandleMutex = new object();
|
||||
|
||||
internal static IntPtr Add(IntPtr handle, int type)
|
||||
{
|
||||
HandleTypes[type - 1].Add();
|
||||
return handle;
|
||||
}
|
||||
|
||||
[System.Security.SecuritySafeCritical]
|
||||
internal static SafeHandle Add(SafeHandle handle, int type)
|
||||
{
|
||||
HandleTypes[type - 1].Add();
|
||||
return handle;
|
||||
}
|
||||
|
||||
internal static void Add(int type)
|
||||
{
|
||||
HandleTypes[type - 1].Add();
|
||||
}
|
||||
|
||||
internal static int RegisterType(string typeName, int expense, int initialThreshold)
|
||||
{
|
||||
lock (HandleMutex)
|
||||
{
|
||||
if (HandleTypeCount == 0 || HandleTypeCount == HandleTypes.Length)
|
||||
{
|
||||
HandleType[] newTypes = new HandleType[HandleTypeCount + 10];
|
||||
if (HandleTypes != null)
|
||||
{
|
||||
Array.Copy(HandleTypes, 0, newTypes, 0, HandleTypeCount);
|
||||
}
|
||||
HandleTypes = newTypes;
|
||||
}
|
||||
|
||||
HandleTypes[HandleTypeCount++] = new HandleType(expense, initialThreshold);
|
||||
return HandleTypeCount;
|
||||
}
|
||||
}
|
||||
|
||||
internal static IntPtr Remove(IntPtr handle, int type)
|
||||
{
|
||||
HandleTypes[type - 1].Remove();
|
||||
return handle;
|
||||
}
|
||||
|
||||
[System.Security.SecuritySafeCritical]
|
||||
internal static SafeHandle Remove(SafeHandle handle, int type)
|
||||
{
|
||||
HandleTypes[type - 1].Remove();
|
||||
return handle;
|
||||
}
|
||||
|
||||
internal static void Remove(int type)
|
||||
{
|
||||
HandleTypes[type - 1].Remove();
|
||||
}
|
||||
|
||||
private class HandleType
|
||||
{
|
||||
private readonly int _initialThreshHold;
|
||||
private int _threshHold;
|
||||
private int _handleCount;
|
||||
private readonly int _deltaPercent;
|
||||
|
||||
internal HandleType(int expense, int initialThreshHold)
|
||||
{
|
||||
_initialThreshHold = initialThreshHold;
|
||||
_threshHold = initialThreshHold;
|
||||
_deltaPercent = 100 - expense;
|
||||
}
|
||||
|
||||
internal void Add()
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
_handleCount++;
|
||||
var performCollect = NeedCollection();
|
||||
|
||||
if (!performCollect)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
GC.Collect();
|
||||
|
||||
var sleep = (100 - _deltaPercent) / 4;
|
||||
System.Threading.Thread.Sleep(sleep);
|
||||
}
|
||||
|
||||
private bool NeedCollection()
|
||||
{
|
||||
|
||||
if (_handleCount > _threshHold)
|
||||
{
|
||||
_threshHold = _handleCount + _handleCount * _deltaPercent / 100;
|
||||
return true;
|
||||
}
|
||||
|
||||
var oldThreshHold = 100 * _threshHold / (100 + _deltaPercent);
|
||||
if (oldThreshHold >= _initialThreshHold && _handleCount < (int) (oldThreshHold * .9F))
|
||||
{
|
||||
_threshHold = oldThreshHold;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal void Remove()
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
_handleCount--;
|
||||
|
||||
_handleCount = Math.Max(0, _handleCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System.Security;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
|
||||
namespace HandyControl.Tools.Interop
|
||||
{
|
||||
internal abstract class WpfSafeHandle : SafeHandleZeroOrMinusOneIsInvalid
|
||||
{
|
||||
private readonly int _collectorId;
|
||||
|
||||
[SecurityCritical]
|
||||
protected WpfSafeHandle(bool ownsHandle, int collectorId) : base(ownsHandle)
|
||||
{
|
||||
HandleCollector.Add(collectorId);
|
||||
_collectorId = collectorId;
|
||||
}
|
||||
|
||||
[SecurityCritical, SecuritySafeCritical]
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
HandleCollector.Remove(_collectorId);
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,683 @@
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.ConstrainedExecution;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Security;
|
||||
using System.Security.Permissions;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace HandyControl.Tools.Interop
|
||||
{
|
||||
internal class InteropMethods
|
||||
{
|
||||
#region common
|
||||
|
||||
internal const int E_FAIL = unchecked((int) 0x80004005);
|
||||
|
||||
internal static readonly IntPtr HRGN_NONE = new IntPtr(-1);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.Kernel32, SetLastError = true, CharSet = CharSet.Auto)]
|
||||
internal static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, out InteropValues.TBBUTTON lpBuffer,
|
||||
int dwSize, out int lpNumberOfBytesRead);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.Kernel32, SetLastError = true)]
|
||||
internal static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, out InteropValues.RECT lpBuffer,
|
||||
int dwSize, out int lpNumberOfBytesRead);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.Kernel32, SetLastError = true)]
|
||||
internal static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, out InteropValues.TRAYDATA lpBuffer,
|
||||
int dwSize, out int lpNumberOfBytesRead);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, CharSet = CharSet.Auto)]
|
||||
internal static extern uint SendMessage(IntPtr hWnd, uint Msg, uint wParam, IntPtr lParam);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, SetLastError = true, CharSet = CharSet.Auto)]
|
||||
internal static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, SetLastError = true, CharSet = CharSet.Auto)]
|
||||
internal static extern bool AttachThreadInput(in uint currentForegroundWindowThreadId,
|
||||
in uint thisWindowThreadId, bool isAttach);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, SetLastError = true, CharSet = CharSet.Auto)]
|
||||
internal static extern IntPtr GetForegroundWindow();
|
||||
|
||||
[DllImport(InteropValues.ExternDll.Kernel32, SetLastError = true, CharSet = CharSet.Auto)]
|
||||
internal static extern IntPtr OpenProcess(InteropValues.ProcessAccess dwDesiredAccess,
|
||||
[MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwProcessId);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.Kernel32, SetLastError = true, CharSet = CharSet.Auto)]
|
||||
internal static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, int dwSize,
|
||||
InteropValues.AllocationType flAllocationType, InteropValues.MemoryProtection flProtect);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.Kernel32, SetLastError = true, CharSet = CharSet.Auto)]
|
||||
internal static extern int CloseHandle(IntPtr hObject);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.Kernel32, SetLastError = true, CharSet = CharSet.Auto)]
|
||||
internal static extern bool VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress, int dwSize, InteropValues.FreeType dwFreeType);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, SetLastError = true)]
|
||||
internal static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass,
|
||||
string lpszWindow);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
internal static extern int GetWindowRect(IntPtr hwnd, out InteropValues.RECT lpRect);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, CharSet = CharSet.Auto)]
|
||||
internal static extern bool GetCursorPos(out InteropValues.POINT pt);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
internal static extern IntPtr GetDesktopWindow();
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool AddClipboardFormatListener(IntPtr hwnd);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool RemoveClipboardFormatListener(IntPtr hwnd);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
internal static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
internal static extern bool EnableMenuItem(IntPtr hMenu, int UIDEnabledItem, int uEnable);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
internal static extern bool InsertMenu(IntPtr hMenu, int wPosition, int wFlags, int wIDNewItem, string lpNewItem);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, ExactSpelling = true, EntryPoint = "DestroyMenu", CharSet = CharSet.Auto)]
|
||||
[ResourceExposure(ResourceScope.None)]
|
||||
internal static extern bool IntDestroyMenu(HandleRef hMenu);
|
||||
|
||||
[SecurityCritical]
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
[DllImport(InteropValues.ExternDll.User32, SetLastError = true, ExactSpelling = true, EntryPoint = nameof(GetDC),
|
||||
CharSet = CharSet.Auto)]
|
||||
internal static extern IntPtr IntGetDC(HandleRef hWnd);
|
||||
|
||||
[SecurityCritical]
|
||||
internal static IntPtr GetDC(HandleRef hWnd)
|
||||
{
|
||||
var hDc = IntGetDC(hWnd);
|
||||
if (hDc == IntPtr.Zero) throw new Win32Exception();
|
||||
|
||||
return HandleCollector.Add(hDc, CommonHandles.HDC);
|
||||
}
|
||||
|
||||
[SecurityCritical]
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
[DllImport(InteropValues.ExternDll.User32, ExactSpelling = true, EntryPoint = nameof(ReleaseDC), CharSet = CharSet.Auto)]
|
||||
internal static extern int IntReleaseDC(HandleRef hWnd, HandleRef hDC);
|
||||
|
||||
[SecurityCritical]
|
||||
internal static int ReleaseDC(HandleRef hWnd, HandleRef hDC)
|
||||
{
|
||||
HandleCollector.Remove((IntPtr) hDC, CommonHandles.HDC);
|
||||
return IntReleaseDC(hWnd, hDC);
|
||||
}
|
||||
|
||||
[SecurityCritical]
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
[DllImport(InteropValues.ExternDll.Gdi32, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Auto)]
|
||||
internal static extern int GetDeviceCaps(HandleRef hDC, int nIndex);
|
||||
|
||||
[SecurityCritical]
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
internal static extern int GetSystemMetrics(InteropValues.SM nIndex);
|
||||
|
||||
[SecurityCritical]
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
[DllImport(InteropValues.ExternDll.User32, EntryPoint = nameof(DestroyIcon), CharSet = CharSet.Auto, SetLastError = true)]
|
||||
private static extern bool IntDestroyIcon(IntPtr hIcon);
|
||||
|
||||
[SecurityCritical]
|
||||
internal static bool DestroyIcon(IntPtr hIcon)
|
||||
{
|
||||
var result = IntDestroyIcon(hIcon);
|
||||
return result;
|
||||
}
|
||||
|
||||
[SecurityCritical]
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
[DllImport(InteropValues.ExternDll.Gdi32, EntryPoint = nameof(DeleteObject), CharSet = CharSet.Auto, SetLastError = true)]
|
||||
private static extern bool IntDeleteObject(IntPtr hObject);
|
||||
|
||||
[SecurityCritical]
|
||||
internal static bool DeleteObject(IntPtr hObject)
|
||||
{
|
||||
var result = IntDeleteObject(hObject);
|
||||
return result;
|
||||
}
|
||||
|
||||
//[SecurityCritical]
|
||||
//internal static BitmapHandle CreateDIBSection(HandleRef hdc, ref InteropValues.BITMAPINFO bitmapInfo, int iUsage,
|
||||
// ref IntPtr ppvBits, SafeFileMappingHandle hSection, int dwOffset)
|
||||
//{
|
||||
// hSection ??= new SafeFileMappingHandle(IntPtr.Zero);
|
||||
|
||||
// var hBitmap = PrivateCreateDIBSection(hdc, ref bitmapInfo, iUsage, ref ppvBits, hSection, dwOffset);
|
||||
// return hBitmap;
|
||||
//}
|
||||
|
||||
[DllImport(InteropValues.ExternDll.Kernel32, EntryPoint = "CloseHandle", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
internal static extern bool IntCloseHandle(HandleRef handle);
|
||||
|
||||
//[SecurityCritical]
|
||||
//[SuppressUnmanagedCodeSecurity]
|
||||
//[DllImport(InteropValues.ExternDll.Gdi32, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Auto,
|
||||
// EntryPoint = nameof(CreateDIBSection))]
|
||||
//private static extern BitmapHandle PrivateCreateDIBSection(HandleRef hdc, ref InteropValues.BITMAPINFO bitmapInfo, int iUsage,
|
||||
// ref IntPtr ppvBits, SafeFileMappingHandle hSection, int dwOffset);
|
||||
|
||||
//[SecurityCritical]
|
||||
//[SuppressUnmanagedCodeSecurity]
|
||||
//[DllImport(InteropValues.ExternDll.User32, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Auto,
|
||||
// EntryPoint = nameof(CreateIconIndirect))]
|
||||
//private static extern IconHandle PrivateCreateIconIndirect([In] [MarshalAs(UnmanagedType.LPStruct)]
|
||||
// InteropValues.ICONINFO iconInfo);
|
||||
|
||||
//[SecurityCritical]
|
||||
//internal static IconHandle CreateIconIndirect([In] [MarshalAs(UnmanagedType.LPStruct)]
|
||||
// InteropValues.ICONINFO iconInfo)
|
||||
//{
|
||||
// var hIcon = PrivateCreateIconIndirect(iconInfo);
|
||||
// return hIcon;
|
||||
//}
|
||||
|
||||
[SecurityCritical]
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
[DllImport(InteropValues.ExternDll.Gdi32, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Auto,
|
||||
EntryPoint = nameof(CreateBitmap))]
|
||||
private static extern BitmapHandle PrivateCreateBitmap(int width, int height, int planes, int bitsPerPixel,
|
||||
byte[] lpvBits);
|
||||
|
||||
[SecurityCritical]
|
||||
internal static BitmapHandle CreateBitmap(int width, int height, int planes, int bitsPerPixel, byte[] lpvBits)
|
||||
{
|
||||
var hBitmap = PrivateCreateBitmap(width, height, planes, bitsPerPixel, lpvBits);
|
||||
return hBitmap;
|
||||
}
|
||||
|
||||
[SecurityCritical]
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
[DllImport(InteropValues.ExternDll.Kernel32, EntryPoint = "GetModuleFileName", CharSet = CharSet.Unicode,
|
||||
SetLastError = true)]
|
||||
private static extern int IntGetModuleFileName(HandleRef hModule, StringBuilder buffer, int length);
|
||||
|
||||
[SecurityCritical]
|
||||
internal static string GetModuleFileName(HandleRef hModule)
|
||||
{
|
||||
var buffer = new StringBuilder(InteropValues.Win32Constant.MAX_PATH);
|
||||
while (true)
|
||||
{
|
||||
var size = IntGetModuleFileName(hModule, buffer, buffer.Capacity);
|
||||
if (size == 0) throw new Win32Exception();
|
||||
|
||||
if (size == buffer.Capacity)
|
||||
{
|
||||
buffer.EnsureCapacity(buffer.Capacity * 2);
|
||||
continue;
|
||||
}
|
||||
|
||||
return buffer.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
//[SecurityCritical]
|
||||
//[SuppressUnmanagedCodeSecurity]
|
||||
//[DllImport(InteropValues.ExternDll.Shell32, CharSet = CharSet.Auto, BestFitMapping = false, ThrowOnUnmappableChar = true)]
|
||||
//internal static extern int ExtractIconEx(string szExeFileName, int nIconIndex, out IconHandle phiconLarge,
|
||||
// out IconHandle phiconSmall, int nIcons);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.Shell32, CharSet = CharSet.Auto)]
|
||||
internal static extern int Shell_NotifyIcon(int message, InteropValues.NOTIFYICONDATA pnid);
|
||||
|
||||
[SecurityCritical]
|
||||
[DllImport(InteropValues.ExternDll.User32, SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "CreateWindowExW")]
|
||||
internal static extern IntPtr CreateWindowEx(
|
||||
int dwExStyle,
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string lpClassName,
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string lpWindowName,
|
||||
int dwStyle,
|
||||
int x,
|
||||
int y,
|
||||
int nWidth,
|
||||
int nHeight,
|
||||
IntPtr hWndParent,
|
||||
IntPtr hMenu,
|
||||
IntPtr hInstance,
|
||||
IntPtr lpParam);
|
||||
|
||||
[SecurityCritical]
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
[DllImport(InteropValues.ExternDll.User32, CharSet = CharSet.Unicode, SetLastError = true, BestFitMapping = false)]
|
||||
internal static extern short RegisterClass(InteropValues.WNDCLASS4ICON wc);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, CharSet = CharSet.Auto)]
|
||||
internal static extern IntPtr DefWindowProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, ExactSpelling = true, CharSet = CharSet.Auto)]
|
||||
internal static extern bool SetForegroundWindow(IntPtr hWnd);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, CharSet = CharSet.Auto, SetLastError = true)]
|
||||
internal static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.Kernel32, CharSet = CharSet.Auto, SetLastError = true)]
|
||||
internal static extern IntPtr GetModuleHandle(string lpModuleName);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, CharSet = CharSet.Auto, SetLastError = true)]
|
||||
internal static extern bool UnhookWindowsHookEx(IntPtr hhk);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, CharSet = CharSet.Auto, SetLastError = true)]
|
||||
internal static extern IntPtr SetWindowsHookEx(int idHook, InteropValues.HookProc lpfn, IntPtr hMod, uint dwThreadId);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, SetLastError = true)]
|
||||
internal static extern IntPtr GetWindowDC(IntPtr window);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.Gdi32, SetLastError = true)]
|
||||
internal static extern uint GetPixel(IntPtr dc, int x, int y);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, SetLastError = true)]
|
||||
internal static extern int ReleaseDC(IntPtr window, IntPtr dc);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.Gdi32, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Auto)]
|
||||
internal static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, CharSet = CharSet.Auto)]
|
||||
internal static extern IntPtr GetDC(IntPtr ptr);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool GetWindowPlacement(IntPtr hwnd, InteropValues.WINDOWPLACEMENT lpwndpl);
|
||||
|
||||
internal static InteropValues.WINDOWPLACEMENT GetWindowPlacement(IntPtr hwnd)
|
||||
{
|
||||
InteropValues.WINDOWPLACEMENT wINDOWPLACEMENT = new InteropValues.WINDOWPLACEMENT();
|
||||
if (GetWindowPlacement(hwnd, wINDOWPLACEMENT))
|
||||
{
|
||||
return wINDOWPLACEMENT;
|
||||
}
|
||||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
}
|
||||
|
||||
internal static int GetXLParam(int lParam) => LoWord(lParam);
|
||||
|
||||
internal static int GetYLParam(int lParam) => HiWord(lParam);
|
||||
|
||||
internal static int HiWord(int value) => (short) (value >> 16);
|
||||
|
||||
internal static int LoWord(int value) => (short) (value & 65535);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
internal static extern IntPtr MonitorFromWindow(IntPtr handle, int flags);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool EnumThreadWindows(uint dwThreadId, InteropValues.EnumWindowsProc lpfn, IntPtr lParam);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.Gdi32)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool DeleteDC(IntPtr hdc);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.Gdi32, SetLastError = true)]
|
||||
internal static extern IntPtr CreateCompatibleDC(IntPtr hdc);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.Gdi32, ExactSpelling = true, SetLastError = true)]
|
||||
internal static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
internal static extern IntPtr SendMessage(IntPtr hWnd, int nMsg, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
internal static extern IntPtr MonitorFromPoint(InteropValues.POINT pt, int flags);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
internal static extern IntPtr GetWindow(IntPtr hwnd, int nCmd);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool IsWindowVisible(IntPtr hwnd);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool IsIconic(IntPtr hwnd);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool IsZoomed(IntPtr hwnd);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, CharSet = CharSet.Auto, ExactSpelling = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, int flags);
|
||||
|
||||
internal static System.Windows.Point GetCursorPos()
|
||||
{
|
||||
var result = default(System.Windows.Point);
|
||||
if (GetCursorPos(out var point))
|
||||
{
|
||||
result.X = point.X;
|
||||
result.Y = point.Y;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
|
||||
|
||||
internal static int GetWindowLong(IntPtr hWnd, InteropValues.GWL nIndex) => GetWindowLong(hWnd, (int) nIndex);
|
||||
|
||||
internal static IntPtr SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong)
|
||||
{
|
||||
if (IntPtr.Size == 4)
|
||||
{
|
||||
return SetWindowLongPtr32(hWnd, nIndex, dwNewLong);
|
||||
}
|
||||
return SetWindowLongPtr64(hWnd, nIndex, dwNewLong);
|
||||
}
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, CharSet = CharSet.Auto, EntryPoint = "SetWindowLong")]
|
||||
internal static extern IntPtr SetWindowLongPtr32(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, CharSet = CharSet.Auto, EntryPoint = "SetWindowLongPtr")]
|
||||
internal static extern IntPtr SetWindowLongPtr64(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, CharSet = CharSet.Unicode)]
|
||||
private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, CharSet = CharSet.Unicode)]
|
||||
private static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
|
||||
|
||||
internal static IntPtr SetWindowLongPtr(IntPtr hWnd, InteropValues.GWLP nIndex, IntPtr dwNewLong)
|
||||
{
|
||||
if (IntPtr.Size == 8)
|
||||
{
|
||||
return SetWindowLongPtr(hWnd, (int) nIndex, dwNewLong);
|
||||
}
|
||||
return new IntPtr(SetWindowLong(hWnd, (int) nIndex, dwNewLong.ToInt32()));
|
||||
}
|
||||
|
||||
internal static int SetWindowLong(IntPtr hWnd, InteropValues.GWL nIndex, int dwNewLong) => SetWindowLong(hWnd, (int) nIndex, dwNewLong);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, CharSet = CharSet.Unicode)]
|
||||
internal static extern ushort RegisterClass(ref InteropValues.WNDCLASS lpWndClass);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.Kernel32)]
|
||||
internal static extern uint GetCurrentThreadId();
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32, CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
internal static extern IntPtr CreateWindowEx(int dwExStyle, IntPtr classAtom, string lpWindowName, int dwStyle, int x, int y, int nWidth, int nHeight, IntPtr hWndParent, IntPtr hMenu, IntPtr hInstance, IntPtr lpParam);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool DestroyWindow(IntPtr hwnd);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool UnregisterClass(IntPtr classAtom, IntPtr hInstance);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
internal static extern bool RedrawWindow(IntPtr hWnd, IntPtr lprcUpdate, IntPtr hrgnUpdate, InteropValues.RedrawWindowFlags flags);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip, InteropValues.EnumMonitorsDelegate lpfnEnum, IntPtr dwData);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool IntersectRect(out InteropValues.RECT lprcDst, [In] ref InteropValues.RECT lprcSrc1, [In] ref InteropValues.RECT lprcSrc2);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool GetMonitorInfo(IntPtr hMonitor, ref InteropValues.MONITORINFO monitorInfo);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.Gdi32, SetLastError = true)]
|
||||
internal static extern IntPtr CreateDIBSection(IntPtr hdc, ref InteropValues.BITMAPINFO pbmi, uint iUsage, out IntPtr ppvBits, IntPtr hSection, uint dwOffset);
|
||||
|
||||
internal static int GET_SC_WPARAM(IntPtr wParam) => (int) wParam & 65520;
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
internal static extern IntPtr ChildWindowFromPointEx(IntPtr hwndParent, InteropValues.POINT pt, int uFlags);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.Gdi32)]
|
||||
internal static extern IntPtr CreateCompatibleBitmap(IntPtr hDC, int width, int height);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.Gdi32)]
|
||||
internal static extern bool BitBlt(IntPtr hDC, int x, int y, int nWidth, int nHeight, IntPtr hSrcDC, int xSrc, int ySrc, int dwRop);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
[ResourceExposure(ResourceScope.None)]
|
||||
internal static extern bool EnableWindow(IntPtr hWnd, bool enable);
|
||||
|
||||
internal static object? PtrToStructure(IntPtr lparam, Type cls) => Marshal.PtrToStructure(lparam, cls);
|
||||
|
||||
internal static void PtrToStructure(IntPtr lparam, object data) => Marshal.PtrToStructure(lparam, data);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.Shell32, CallingConvention = CallingConvention.StdCall)]
|
||||
internal static extern uint SHAppBarMessage(int dwMessage, ref InteropValues.APPBARDATA pData);
|
||||
|
||||
[SecurityCritical]
|
||||
[DllImport(InteropValues.ExternDll.DwmApi, EntryPoint = "DwmGetColorizationColor", PreserveSig = true)]
|
||||
internal static extern int DwmGetColorizationColor(out uint pcrColorization, out bool pfOpaqueBlend);
|
||||
|
||||
#endregion
|
||||
|
||||
internal class Gdip
|
||||
{
|
||||
private const string ThreadDataSlotName = "system.drawing.threaddata";
|
||||
|
||||
private static IntPtr InitToken;
|
||||
|
||||
private static bool Initialized => InitToken != IntPtr.Zero;
|
||||
|
||||
internal const int
|
||||
Ok = 0,
|
||||
GenericError = 1,
|
||||
InvalidParameter = 2,
|
||||
OutOfMemory = 3,
|
||||
ObjectBusy = 4,
|
||||
InsufficientBuffer = 5,
|
||||
NotImplemented = 6,
|
||||
Win32Error = 7,
|
||||
WrongState = 8,
|
||||
Aborted = 9,
|
||||
FileNotFound = 10,
|
||||
ValueOverflow = 11,
|
||||
AccessDenied = 12,
|
||||
UnknownImageFormat = 13,
|
||||
FontFamilyNotFound = 14,
|
||||
FontStyleNotFound = 15,
|
||||
NotTrueTypeFont = 16,
|
||||
UnsupportedGdiplusVersion = 17,
|
||||
GdiplusNotInitialized = 18,
|
||||
PropertyNotFound = 19,
|
||||
PropertyNotSupported = 20,
|
||||
E_UNEXPECTED = unchecked((int) 0x8000FFFF);
|
||||
|
||||
static Gdip()
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct StartupInput
|
||||
{
|
||||
private int GdiplusVersion;
|
||||
|
||||
private readonly IntPtr DebugEventCallback;
|
||||
|
||||
private bool SuppressBackgroundThread;
|
||||
|
||||
private bool SuppressExternalCodecs;
|
||||
|
||||
public static StartupInput GetDefault()
|
||||
{
|
||||
var result = new StartupInput
|
||||
{
|
||||
GdiplusVersion = 1,
|
||||
SuppressBackgroundThread = false,
|
||||
SuppressExternalCodecs = false
|
||||
};
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private readonly struct StartupOutput
|
||||
{
|
||||
private readonly IntPtr hook;
|
||||
|
||||
private readonly IntPtr unhook;
|
||||
}
|
||||
|
||||
[ResourceExposure(ResourceScope.None)]
|
||||
[ResourceConsumption(ResourceScope.AppDomain, ResourceScope.AppDomain)]
|
||||
private static void Initialize()
|
||||
{
|
||||
var input = StartupInput.GetDefault();
|
||||
|
||||
var status = GdiplusStartup(out InitToken, ref input, out StartupOutput output);
|
||||
|
||||
if (status != Ok)
|
||||
{
|
||||
throw StatusException(status);
|
||||
}
|
||||
|
||||
var currentDomain = AppDomain.CurrentDomain;
|
||||
currentDomain.ProcessExit += OnProcessExit!;
|
||||
|
||||
if (!currentDomain.IsDefaultAppDomain())
|
||||
{
|
||||
currentDomain.DomainUnload += OnProcessExit!;
|
||||
}
|
||||
}
|
||||
|
||||
[ResourceExposure(ResourceScope.AppDomain)]
|
||||
[ResourceConsumption(ResourceScope.AppDomain)]
|
||||
private static void OnProcessExit(object sender, EventArgs e) => Shutdown();
|
||||
|
||||
[ResourceExposure(ResourceScope.AppDomain)]
|
||||
[ResourceConsumption(ResourceScope.AppDomain)]
|
||||
private static void Shutdown()
|
||||
{
|
||||
if (Initialized)
|
||||
{
|
||||
ClearThreadData();
|
||||
// unhook our shutdown handlers as we do not need to shut down more than once
|
||||
var currentDomain = AppDomain.CurrentDomain;
|
||||
currentDomain.ProcessExit -= OnProcessExit!;
|
||||
if (!currentDomain.IsDefaultAppDomain())
|
||||
{
|
||||
currentDomain.DomainUnload -= OnProcessExit!;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ClearThreadData()
|
||||
{
|
||||
var slot = Thread.GetNamedDataSlot(ThreadDataSlotName);
|
||||
Thread.SetData(slot, null);
|
||||
}
|
||||
|
||||
[DllImport(InteropValues.ExternDll.GdiPlus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||
[ResourceExposure(ResourceScope.None)]
|
||||
internal static extern int GdipImageGetFrameDimensionsCount(HandleRef image, out int count);
|
||||
|
||||
internal static Exception StatusException(int status)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
[DllImport(InteropValues.ExternDll.GdiPlus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||
[ResourceExposure(ResourceScope.None)]
|
||||
internal static extern int GdipImageGetFrameDimensionsList(HandleRef image, IntPtr buffer, int count);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.GdiPlus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||
[ResourceExposure(ResourceScope.None)]
|
||||
internal static extern int GdipImageGetFrameCount(HandleRef image, ref Guid dimensionId, int[] count);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.GdiPlus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||
[ResourceExposure(ResourceScope.None)]
|
||||
internal static extern int GdipGetPropertyItemSize(HandleRef image, int propid, out int size);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.GdiPlus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||
[ResourceExposure(ResourceScope.None)]
|
||||
internal static extern int GdipGetPropertyItem(HandleRef image, int propid, int size, IntPtr buffer);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.GdiPlus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||
[ResourceExposure(ResourceScope.Machine)]
|
||||
internal static extern int GdipCreateHBITMAPFromBitmap(HandleRef nativeBitmap, out IntPtr hbitmap, int argbBackground);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.GdiPlus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||
[ResourceExposure(ResourceScope.None)]
|
||||
internal static extern int GdipImageSelectActiveFrame(HandleRef image, ref Guid dimensionId, int frameIndex);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.GdiPlus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||
[ResourceExposure(ResourceScope.Machine)]
|
||||
internal static extern int GdipCreateBitmapFromFile(string filename, out IntPtr bitmap);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.GdiPlus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||
[ResourceExposure(ResourceScope.None)]
|
||||
internal static extern int GdipImageForceValidation(HandleRef image);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.GdiPlus, SetLastError = true, ExactSpelling = true, EntryPoint = "GdipDisposeImage", CharSet = CharSet.Unicode)]
|
||||
[ResourceExposure(ResourceScope.None)]
|
||||
private static extern int IntGdipDisposeImage(HandleRef image);
|
||||
|
||||
internal static int GdipDisposeImage(HandleRef image)
|
||||
{
|
||||
if (!Initialized) return Ok;
|
||||
var result = IntGdipDisposeImage(image);
|
||||
return result;
|
||||
}
|
||||
|
||||
[DllImport(InteropValues.ExternDll.GdiPlus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||
[ResourceExposure(ResourceScope.Process)]
|
||||
private static extern int GdiplusStartup(out IntPtr token, ref StartupInput input, out StartupOutput output);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.GdiPlus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||
[ResourceExposure(ResourceScope.None)]
|
||||
internal static extern int GdipGetImageRawFormat(HandleRef image, ref Guid format);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.User32)]
|
||||
internal static extern int SetWindowCompositionAttribute(IntPtr hwnd, ref InteropValues.WINCOMPATTRDATA data);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.GdiPlus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||
[ResourceExposure(ResourceScope.Machine)]
|
||||
internal static extern int GdipCreateBitmapFromStream(InteropValues.IStream stream, out IntPtr bitmap);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.GdiPlus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||
[ResourceExposure(ResourceScope.Machine)]
|
||||
internal static extern int GdipCreateBitmapFromHBITMAP(HandleRef hbitmap, HandleRef hpalette, out IntPtr bitmap);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.GdiPlus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||
[ResourceExposure(ResourceScope.None)]
|
||||
internal static extern int GdipGetImageEncodersSize(out int numEncoders, out int size);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.GdiPlus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||
[ResourceExposure(ResourceScope.None)]
|
||||
internal static extern int GdipGetImageDecodersSize(out int numDecoders, out int size);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.GdiPlus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||
[ResourceExposure(ResourceScope.None)]
|
||||
internal static extern int GdipGetImageDecoders(int numDecoders, int size, IntPtr decoders);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.GdiPlus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||
[ResourceExposure(ResourceScope.None)]
|
||||
internal static extern int GdipGetImageEncoders(int numEncoders, int size, IntPtr encoders);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.GdiPlus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||
[ResourceExposure(ResourceScope.None)]
|
||||
internal static extern int GdipSaveImageToStream(HandleRef image, InteropValues.IStream stream, ref Guid classId, HandleRef encoderParams);
|
||||
|
||||
[DllImport(InteropValues.ExternDll.NTdll)]
|
||||
internal static extern int RtlGetVersion(out InteropValues.RTL_OSVERSIONINFOEX lpVersionInformation);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,975 @@
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
using System.Windows;
|
||||
|
||||
namespace HandyControl.Tools.Interop
|
||||
{
|
||||
internal class InteropValues
|
||||
{
|
||||
internal static class ExternDll
|
||||
{
|
||||
public const string
|
||||
User32 = "user32.dll",
|
||||
Gdi32 = "gdi32.dll",
|
||||
GdiPlus = "gdiplus.dll",
|
||||
Kernel32 = "kernel32.dll",
|
||||
Shell32 = "shell32.dll",
|
||||
MsImg = "msimg32.dll",
|
||||
NTdll = "ntdll.dll",
|
||||
DwmApi = "dwmapi.dll";
|
||||
}
|
||||
|
||||
internal delegate IntPtr HookProc(int code, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
internal delegate IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal delegate bool EnumMonitorsDelegate(IntPtr hMonitor, IntPtr hdcMonitor, ref RECT lprcMonitor, IntPtr dwData);
|
||||
|
||||
internal delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
|
||||
|
||||
internal const int
|
||||
BITSPIXEL = 12,
|
||||
PLANES = 14,
|
||||
BI_RGB = 0,
|
||||
DIB_RGB_COLORS = 0,
|
||||
E_FAIL = unchecked((int) 0x80004005),
|
||||
NIF_MESSAGE = 0x00000001,
|
||||
NIF_ICON = 0x00000002,
|
||||
NIF_TIP = 0x00000004,
|
||||
NIF_INFO = 0x00000010,
|
||||
NIM_ADD = 0x00000000,
|
||||
NIM_MODIFY = 0x00000001,
|
||||
NIM_DELETE = 0x00000002,
|
||||
NIIF_NONE = 0x00000000,
|
||||
NIIF_INFO = 0x00000001,
|
||||
NIIF_WARNING = 0x00000002,
|
||||
NIIF_ERROR = 0x00000003,
|
||||
WM_ACTIVATE = 0x0006,
|
||||
WM_QUIT = 0x0012,
|
||||
WM_GETMINMAXINFO = 0x0024,
|
||||
WM_WINDOWPOSCHANGING = 0x0046,
|
||||
WM_WINDOWPOSCHANGED = 0x0047,
|
||||
WM_SETICON = 0x0080,
|
||||
WM_NCCREATE = 0x0081,
|
||||
WM_NCDESTROY = 0x0082,
|
||||
WM_NCHITTEST = 0x0084,
|
||||
WM_NCACTIVATE = 0x0086,
|
||||
WM_NCRBUTTONDOWN = 0x00A4,
|
||||
WM_NCRBUTTONUP = 0x00A5,
|
||||
WM_NCRBUTTONDBLCLK = 0x00A6,
|
||||
WM_NCUAHDRAWCAPTION = 0x00AE,
|
||||
WM_NCUAHDRAWFRAME = 0x00AF,
|
||||
WM_KEYDOWN = 0x0100,
|
||||
WM_KEYUP = 0x0101,
|
||||
WM_SYSKEYDOWN = 0x0104,
|
||||
WM_SYSKEYUP = 0x0105,
|
||||
WM_SYSCOMMAND = 0x112,
|
||||
WM_MOUSEMOVE = 0x0200,
|
||||
WM_LBUTTONUP = 0x0202,
|
||||
WM_LBUTTONDBLCLK = 0x0203,
|
||||
WM_RBUTTONUP = 0x0205,
|
||||
WM_ENTERSIZEMOVE = 0x0231,
|
||||
WM_EXITSIZEMOVE = 0x0232,
|
||||
WM_CLIPBOARDUPDATE = 0x031D,
|
||||
WM_USER = 0x0400,
|
||||
WS_VISIBLE = 0x10000000,
|
||||
MF_BYCOMMAND = 0x00000000,
|
||||
MF_BYPOSITION = 0x400,
|
||||
MF_GRAYED = 0x00000001,
|
||||
MF_SEPARATOR = 0x800,
|
||||
TB_GETBUTTON = WM_USER + 23,
|
||||
TB_BUTTONCOUNT = WM_USER + 24,
|
||||
TB_GETITEMRECT = WM_USER + 29,
|
||||
VERTRES = 10,
|
||||
DESKTOPVERTRES = 117,
|
||||
LOGPIXELSX = 88,
|
||||
LOGPIXELSY = 90,
|
||||
SC_CLOSE = 0xF060,
|
||||
SC_SIZE = 0xF000,
|
||||
SC_MOVE = 0xF010,
|
||||
SC_MINIMIZE = 0xF020,
|
||||
SC_MAXIMIZE = 0xF030,
|
||||
SC_RESTORE = 0xF120,
|
||||
SRCCOPY = 0x00CC0020,
|
||||
MONITOR_DEFAULTTONEAREST = 0x00000002;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
||||
internal class NOTIFYICONDATA
|
||||
{
|
||||
public int cbSize = Marshal.SizeOf(typeof(NOTIFYICONDATA));
|
||||
public IntPtr hWnd;
|
||||
public int uID;
|
||||
public int uFlags;
|
||||
public int uCallbackMessage;
|
||||
public IntPtr hIcon;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
|
||||
public string szTip = string.Empty;
|
||||
public int dwState = 0x01;
|
||||
public int dwStateMask = 0x01;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
|
||||
public string szInfo = string.Empty;
|
||||
public int uTimeoutOrVersion;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
|
||||
public string szInfoTitle = string.Empty;
|
||||
public int dwInfoFlags;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
internal struct TBBUTTON
|
||||
{
|
||||
public int iBitmap;
|
||||
public int idCommand;
|
||||
public IntPtr fsStateStylePadding;
|
||||
public IntPtr dwData;
|
||||
public IntPtr iString;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum AllocationType
|
||||
{
|
||||
Commit = 0x1000,
|
||||
Reserve = 0x2000,
|
||||
Decommit = 0x4000,
|
||||
Release = 0x8000,
|
||||
Reset = 0x80000,
|
||||
Physical = 0x400000,
|
||||
TopDown = 0x100000,
|
||||
WriteWatch = 0x200000,
|
||||
LargePages = 0x20000000
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum MemoryProtection
|
||||
{
|
||||
Execute = 0x10,
|
||||
ExecuteRead = 0x20,
|
||||
ExecuteReadWrite = 0x40,
|
||||
ExecuteWriteCopy = 0x80,
|
||||
NoAccess = 0x01,
|
||||
ReadOnly = 0x02,
|
||||
ReadWrite = 0x04,
|
||||
WriteCopy = 0x08,
|
||||
GuardModifierflag = 0x100,
|
||||
NoCacheModifierflag = 0x200,
|
||||
WriteCombineModifierflag = 0x400
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct TRAYDATA
|
||||
{
|
||||
public IntPtr hwnd;
|
||||
public uint uID;
|
||||
public uint uCallbackMessage;
|
||||
public uint bReserved0;
|
||||
public uint bReserved1;
|
||||
public IntPtr hIcon;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum FreeType
|
||||
{
|
||||
Decommit = 0x4000,
|
||||
Release = 0x8000,
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct POINT
|
||||
{
|
||||
public int X;
|
||||
public int Y;
|
||||
|
||||
public POINT(int x, int y)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
}
|
||||
}
|
||||
|
||||
internal enum HookType
|
||||
{
|
||||
WH_KEYBOARD_LL = 13,
|
||||
WH_MOUSE_LL = 14
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct MOUSEHOOKSTRUCT
|
||||
{
|
||||
public POINT pt;
|
||||
public IntPtr hwnd;
|
||||
public uint wHitTestCode;
|
||||
public IntPtr dwExtraInfo;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum ProcessAccess
|
||||
{
|
||||
AllAccess = CreateThread | DuplicateHandle | QueryInformation | SetInformation | Terminate | VMOperation | VMRead | VMWrite | Synchronize,
|
||||
CreateThread = 0x2,
|
||||
DuplicateHandle = 0x40,
|
||||
QueryInformation = 0x400,
|
||||
SetInformation = 0x200,
|
||||
Terminate = 0x1,
|
||||
VMOperation = 0x8,
|
||||
VMRead = 0x10,
|
||||
VMWrite = 0x20,
|
||||
Synchronize = 0x100000
|
||||
}
|
||||
|
||||
[Serializable, StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
||||
internal struct RECT
|
||||
{
|
||||
public int Left;
|
||||
public int Top;
|
||||
public int Right;
|
||||
public int Bottom;
|
||||
|
||||
public RECT(int left, int top, int right, int bottom)
|
||||
{
|
||||
Left = left;
|
||||
Top = top;
|
||||
Right = right;
|
||||
Bottom = bottom;
|
||||
}
|
||||
|
||||
public RECT(Rect rect)
|
||||
{
|
||||
Left = (int) rect.Left;
|
||||
Top = (int) rect.Top;
|
||||
Right = (int) rect.Right;
|
||||
Bottom = (int) rect.Bottom;
|
||||
}
|
||||
|
||||
public readonly Point Position => new Point(Left, Top);
|
||||
public readonly Size Size => new Size(Width, Height);
|
||||
|
||||
public int Height
|
||||
{
|
||||
readonly get => Bottom - Top;
|
||||
set => Bottom = Top + value;
|
||||
}
|
||||
|
||||
public int Width
|
||||
{
|
||||
readonly get => Right - Left;
|
||||
set => Right = Left + value;
|
||||
}
|
||||
}
|
||||
|
||||
internal enum GWL
|
||||
{
|
||||
STYLE = -16,
|
||||
EXSTYLE = -20
|
||||
}
|
||||
|
||||
internal enum GWLP
|
||||
{
|
||||
WNDPROC = -4,
|
||||
HINSTANCE = -6,
|
||||
HWNDPARENT = -8,
|
||||
USERDATA = -21,
|
||||
ID = -12
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum RedrawWindowFlags : uint
|
||||
{
|
||||
Invalidate = 1u,
|
||||
InternalPaint = 2u,
|
||||
Erase = 4u,
|
||||
Validate = 8u,
|
||||
NoInternalPaint = 16u,
|
||||
NoErase = 32u,
|
||||
NoChildren = 64u,
|
||||
AllChildren = 128u,
|
||||
UpdateNow = 256u,
|
||||
EraseNow = 512u,
|
||||
Frame = 1024u,
|
||||
NoFrame = 2048u
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal class WINDOWPOS
|
||||
{
|
||||
public IntPtr hwnd;
|
||||
public IntPtr hwndInsertAfter;
|
||||
public int x;
|
||||
public int y;
|
||||
public int cx;
|
||||
public int cy;
|
||||
public uint flags;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal class WINDOWPLACEMENT
|
||||
{
|
||||
public int length = Marshal.SizeOf(typeof(WINDOWPLACEMENT));
|
||||
public int flags;
|
||||
public int showCmd;
|
||||
public POINT ptMinPosition;
|
||||
public POINT ptMaxPosition;
|
||||
public RECT rcNormalPosition;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||
internal struct SIZE
|
||||
{
|
||||
[ComAliasName("Microsoft.VisualStudio.OLE.Interop.LONG")]
|
||||
public int cx;
|
||||
[ComAliasName("Microsoft.VisualStudio.OLE.Interop.LONG")]
|
||||
public int cy;
|
||||
}
|
||||
|
||||
internal struct MONITORINFO
|
||||
{
|
||||
public uint cbSize;
|
||||
public RECT rcMonitor;
|
||||
public RECT rcWork;
|
||||
public uint dwFlags;
|
||||
}
|
||||
|
||||
internal enum SM
|
||||
{
|
||||
CXSCREEN = 0,
|
||||
CYSCREEN = 1,
|
||||
CXVSCROLL = 2,
|
||||
CYHSCROLL = 3,
|
||||
CYCAPTION = 4,
|
||||
CXBORDER = 5,
|
||||
CYBORDER = 6,
|
||||
CXFIXEDFRAME = 7,
|
||||
CYFIXEDFRAME = 8,
|
||||
CYVTHUMB = 9,
|
||||
CXHTHUMB = 10,
|
||||
CXICON = 11,
|
||||
CYICON = 12,
|
||||
CXCURSOR = 13,
|
||||
CYCURSOR = 14,
|
||||
CYMENU = 15,
|
||||
CXFULLSCREEN = 16,
|
||||
CYFULLSCREEN = 17,
|
||||
CYKANJIWINDOW = 18,
|
||||
MOUSEPRESENT = 19,
|
||||
CYVSCROLL = 20,
|
||||
CXHSCROLL = 21,
|
||||
DEBUG = 22,
|
||||
SWAPBUTTON = 23,
|
||||
CXMIN = 28,
|
||||
CYMIN = 29,
|
||||
CXSIZE = 30,
|
||||
CYSIZE = 31,
|
||||
CXFRAME = 32,
|
||||
CXSIZEFRAME = CXFRAME,
|
||||
CYFRAME = 33,
|
||||
CYSIZEFRAME = CYFRAME,
|
||||
CXMINTRACK = 34,
|
||||
CYMINTRACK = 35,
|
||||
CXDOUBLECLK = 36,
|
||||
CYDOUBLECLK = 37,
|
||||
CXICONSPACING = 38,
|
||||
CYICONSPACING = 39,
|
||||
MENUDROPALIGNMENT = 40,
|
||||
PENWINDOWS = 41,
|
||||
DBCSENABLED = 42,
|
||||
CMOUSEBUTTONS = 43,
|
||||
SECURE = 44,
|
||||
CXEDGE = 45,
|
||||
CYEDGE = 46,
|
||||
CXMINSPACING = 47,
|
||||
CYMINSPACING = 48,
|
||||
CXSMICON = 49,
|
||||
CYSMICON = 50,
|
||||
CYSMCAPTION = 51,
|
||||
CXSMSIZE = 52,
|
||||
CYSMSIZE = 53,
|
||||
CXMENUSIZE = 54,
|
||||
CYMENUSIZE = 55,
|
||||
ARRANGE = 56,
|
||||
CXMINIMIZED = 57,
|
||||
CYMINIMIZED = 58,
|
||||
CXMAXTRACK = 59,
|
||||
CYMAXTRACK = 60,
|
||||
CXMAXIMIZED = 61,
|
||||
CYMAXIMIZED = 62,
|
||||
NETWORK = 63,
|
||||
CLEANBOOT = 67,
|
||||
CXDRAG = 68,
|
||||
CYDRAG = 69,
|
||||
SHOWSOUNDS = 70,
|
||||
CXMENUCHECK = 71,
|
||||
CYMENUCHECK = 72,
|
||||
SLOWMACHINE = 73,
|
||||
MIDEASTENABLED = 74,
|
||||
MOUSEWHEELPRESENT = 75,
|
||||
XVIRTUALSCREEN = 76,
|
||||
YVIRTUALSCREEN = 77,
|
||||
CXVIRTUALSCREEN = 78,
|
||||
CYVIRTUALSCREEN = 79,
|
||||
CMONITORS = 80,
|
||||
SAMEDISPLAYFORMAT = 81,
|
||||
IMMENABLED = 82,
|
||||
CXFOCUSBORDER = 83,
|
||||
CYFOCUSBORDER = 84,
|
||||
TABLETPC = 86,
|
||||
MEDIACENTER = 87,
|
||||
REMOTESESSION = 0x1000,
|
||||
REMOTECONTROL = 0x2001
|
||||
}
|
||||
|
||||
internal enum CacheSlot
|
||||
{
|
||||
DpiX,
|
||||
|
||||
FocusBorderWidth,
|
||||
FocusBorderHeight,
|
||||
HighContrast,
|
||||
MouseVanish,
|
||||
|
||||
DropShadow,
|
||||
FlatMenu,
|
||||
WorkAreaInternal,
|
||||
WorkArea,
|
||||
|
||||
IconMetrics,
|
||||
|
||||
KeyboardCues,
|
||||
KeyboardDelay,
|
||||
KeyboardPreference,
|
||||
KeyboardSpeed,
|
||||
SnapToDefaultButton,
|
||||
WheelScrollLines,
|
||||
MouseHoverTime,
|
||||
MouseHoverHeight,
|
||||
MouseHoverWidth,
|
||||
|
||||
MenuDropAlignment,
|
||||
MenuFade,
|
||||
MenuShowDelay,
|
||||
|
||||
ComboBoxAnimation,
|
||||
ClientAreaAnimation,
|
||||
CursorShadow,
|
||||
GradientCaptions,
|
||||
HotTracking,
|
||||
ListBoxSmoothScrolling,
|
||||
MenuAnimation,
|
||||
SelectionFade,
|
||||
StylusHotTracking,
|
||||
ToolTipAnimation,
|
||||
ToolTipFade,
|
||||
UIEffects,
|
||||
|
||||
MinimizeAnimation,
|
||||
Border,
|
||||
CaretWidth,
|
||||
ForegroundFlashCount,
|
||||
DragFullWindows,
|
||||
NonClientMetrics,
|
||||
|
||||
ThinHorizontalBorderHeight,
|
||||
ThinVerticalBorderWidth,
|
||||
CursorWidth,
|
||||
CursorHeight,
|
||||
ThickHorizontalBorderHeight,
|
||||
ThickVerticalBorderWidth,
|
||||
MinimumHorizontalDragDistance,
|
||||
MinimumVerticalDragDistance,
|
||||
FixedFrameHorizontalBorderHeight,
|
||||
FixedFrameVerticalBorderWidth,
|
||||
FocusHorizontalBorderHeight,
|
||||
FocusVerticalBorderWidth,
|
||||
FullPrimaryScreenWidth,
|
||||
FullPrimaryScreenHeight,
|
||||
HorizontalScrollBarButtonWidth,
|
||||
HorizontalScrollBarHeight,
|
||||
HorizontalScrollBarThumbWidth,
|
||||
IconWidth,
|
||||
IconHeight,
|
||||
IconGridWidth,
|
||||
IconGridHeight,
|
||||
MaximizedPrimaryScreenWidth,
|
||||
MaximizedPrimaryScreenHeight,
|
||||
MaximumWindowTrackWidth,
|
||||
MaximumWindowTrackHeight,
|
||||
MenuCheckmarkWidth,
|
||||
MenuCheckmarkHeight,
|
||||
MenuButtonWidth,
|
||||
MenuButtonHeight,
|
||||
MinimumWindowWidth,
|
||||
MinimumWindowHeight,
|
||||
MinimizedWindowWidth,
|
||||
MinimizedWindowHeight,
|
||||
MinimizedGridWidth,
|
||||
MinimizedGridHeight,
|
||||
MinimumWindowTrackWidth,
|
||||
MinimumWindowTrackHeight,
|
||||
PrimaryScreenWidth,
|
||||
PrimaryScreenHeight,
|
||||
WindowCaptionButtonWidth,
|
||||
WindowCaptionButtonHeight,
|
||||
ResizeFrameHorizontalBorderHeight,
|
||||
ResizeFrameVerticalBorderWidth,
|
||||
SmallIconWidth,
|
||||
SmallIconHeight,
|
||||
SmallWindowCaptionButtonWidth,
|
||||
SmallWindowCaptionButtonHeight,
|
||||
VirtualScreenWidth,
|
||||
VirtualScreenHeight,
|
||||
VerticalScrollBarWidth,
|
||||
VerticalScrollBarButtonHeight,
|
||||
WindowCaptionHeight,
|
||||
KanjiWindowHeight,
|
||||
MenuBarHeight,
|
||||
VerticalScrollBarThumbHeight,
|
||||
IsImmEnabled,
|
||||
IsMediaCenter,
|
||||
IsMenuDropRightAligned,
|
||||
IsMiddleEastEnabled,
|
||||
IsMousePresent,
|
||||
IsMouseWheelPresent,
|
||||
IsPenWindows,
|
||||
IsRemotelyControlled,
|
||||
IsRemoteSession,
|
||||
ShowSounds,
|
||||
IsSlowMachine,
|
||||
SwapButtons,
|
||||
IsTabletPC,
|
||||
VirtualScreenLeft,
|
||||
VirtualScreenTop,
|
||||
|
||||
PowerLineStatus,
|
||||
|
||||
IsGlassEnabled,
|
||||
UxThemeName,
|
||||
UxThemeColor,
|
||||
WindowCornerRadius,
|
||||
WindowGlassColor,
|
||||
WindowGlassBrush,
|
||||
WindowNonClientFrameThickness,
|
||||
WindowResizeBorderThickness,
|
||||
|
||||
NumSlots
|
||||
}
|
||||
|
||||
internal static class Win32Constant
|
||||
{
|
||||
internal const int MAX_PATH = 260;
|
||||
internal const int INFOTIPSIZE = 1024;
|
||||
internal const int TRUE = 1;
|
||||
internal const int FALSE = 0;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
internal struct WNDCLASS
|
||||
{
|
||||
public uint style;
|
||||
public Delegate lpfnWndProc;
|
||||
public int cbClsExtra;
|
||||
public int cbWndExtra;
|
||||
public IntPtr hInstance;
|
||||
public IntPtr hIcon;
|
||||
public IntPtr hCursor;
|
||||
public IntPtr hbrBackground;
|
||||
[MarshalAs(UnmanagedType.LPWStr)]
|
||||
public string lpszMenuName;
|
||||
[MarshalAs(UnmanagedType.LPWStr)]
|
||||
public string lpszClassName;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
||||
internal class WNDCLASS4ICON
|
||||
{
|
||||
public int style;
|
||||
public WndProc? lpfnWndProc;
|
||||
public int cbClsExtra;
|
||||
public int cbWndExtra;
|
||||
public IntPtr hInstance;
|
||||
public IntPtr hIcon;
|
||||
public IntPtr hCursor;
|
||||
public IntPtr hbrBackground;
|
||||
public string? lpszMenuName;
|
||||
public string? lpszClassName;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 2)]
|
||||
internal struct BITMAPINFO
|
||||
{
|
||||
public int biSize;
|
||||
|
||||
public int biWidth;
|
||||
|
||||
public int biHeight;
|
||||
|
||||
public short biPlanes;
|
||||
|
||||
public short biBitCount;
|
||||
|
||||
public int biCompression;
|
||||
|
||||
public int biSizeImage;
|
||||
|
||||
public int biXPelsPerMeter;
|
||||
|
||||
public int biYPelsPerMeter;
|
||||
|
||||
public int biClrUsed;
|
||||
|
||||
public int biClrImportant;
|
||||
|
||||
public BITMAPINFO(int width, int height, short bpp)
|
||||
{
|
||||
biSize = SizeOf();
|
||||
biWidth = width;
|
||||
biHeight = height;
|
||||
biPlanes = 1;
|
||||
biBitCount = bpp;
|
||||
biCompression = 0;
|
||||
biSizeImage = 0;
|
||||
biXPelsPerMeter = 0;
|
||||
biYPelsPerMeter = 0;
|
||||
biClrUsed = 0;
|
||||
biClrImportant = 0;
|
||||
}
|
||||
|
||||
[SecuritySafeCritical]
|
||||
private static int SizeOf()
|
||||
{
|
||||
return Marshal.SizeOf(typeof(BITMAPINFO));
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal class ICONINFO
|
||||
{
|
||||
public bool fIcon = false;
|
||||
public int xHotspot = 0;
|
||||
public int yHotspot = 0;
|
||||
public BitmapHandle? hbmMask = null;
|
||||
public BitmapHandle? hbmColor = null;
|
||||
}
|
||||
|
||||
internal enum WINDOWCOMPOSITIONATTRIB
|
||||
{
|
||||
WCA_ACCENT_POLICY = 19
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct WINCOMPATTRDATA
|
||||
{
|
||||
public WINDOWCOMPOSITIONATTRIB Attribute;
|
||||
public IntPtr Data;
|
||||
public int DataSize;
|
||||
}
|
||||
|
||||
internal enum ACCENTSTATE
|
||||
{
|
||||
ACCENT_DISABLED = 0,
|
||||
ACCENT_ENABLE_GRADIENT = 1,
|
||||
ACCENT_ENABLE_TRANSPARENTGRADIENT = 2,
|
||||
ACCENT_ENABLE_BLURBEHIND = 3,
|
||||
ACCENT_ENABLE_ACRYLICBLURBEHIND = 4,
|
||||
ACCENT_INVALID_STATE = 5
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct ACCENTPOLICY
|
||||
{
|
||||
public ACCENTSTATE AccentState;
|
||||
public int AccentFlags;
|
||||
public uint GradientColor;
|
||||
public int AnimationId;
|
||||
}
|
||||
|
||||
[ComImport, Guid("0000000C-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
internal interface IStream
|
||||
{
|
||||
int Read([In] IntPtr buf, [In] int len);
|
||||
|
||||
int Write([In] IntPtr buf, [In] int len);
|
||||
|
||||
[return: MarshalAs(UnmanagedType.I8)]
|
||||
long Seek([In, MarshalAs(UnmanagedType.I8)] long dlibMove, [In] int dwOrigin);
|
||||
|
||||
void SetSize([In, MarshalAs(UnmanagedType.I8)] long libNewSize);
|
||||
|
||||
[return: MarshalAs(UnmanagedType.I8)]
|
||||
long CopyTo([In, MarshalAs(UnmanagedType.Interface)] IStream pstm, [In, MarshalAs(UnmanagedType.I8)] long cb, [Out, MarshalAs(UnmanagedType.LPArray)] long[] pcbRead);
|
||||
|
||||
void Commit([In] int grfCommitFlags);
|
||||
|
||||
void Revert();
|
||||
|
||||
void LockRegion([In, MarshalAs(UnmanagedType.I8)] long libOffset, [In, MarshalAs(UnmanagedType.I8)] long cb, [In] int dwLockType);
|
||||
|
||||
void UnlockRegion([In, MarshalAs(UnmanagedType.I8)] long libOffset, [In, MarshalAs(UnmanagedType.I8)] long cb, [In] int dwLockType);
|
||||
|
||||
void Stat([In] IntPtr pStatstg, [In] int grfStatFlag);
|
||||
|
||||
[return: MarshalAs(UnmanagedType.Interface)]
|
||||
IStream Clone();
|
||||
}
|
||||
|
||||
internal class StreamConsts
|
||||
{
|
||||
public const int LOCK_WRITE = 0x1;
|
||||
public const int LOCK_EXCLUSIVE = 0x2;
|
||||
public const int LOCK_ONLYONCE = 0x4;
|
||||
public const int STATFLAG_DEFAULT = 0x0;
|
||||
public const int STATFLAG_NONAME = 0x1;
|
||||
public const int STATFLAG_NOOPEN = 0x2;
|
||||
public const int STGC_DEFAULT = 0x0;
|
||||
public const int STGC_OVERWRITE = 0x1;
|
||||
public const int STGC_ONLYIFCURRENT = 0x2;
|
||||
public const int STGC_DANGEROUSLYCOMMITMERELYTODISKCACHE = 0x4;
|
||||
public const int STREAM_SEEK_SET = 0x0;
|
||||
public const int STREAM_SEEK_CUR = 0x1;
|
||||
public const int STREAM_SEEK_END = 0x2;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 8)]
|
||||
internal class ImageCodecInfoPrivate
|
||||
{
|
||||
[MarshalAs(UnmanagedType.Struct)]
|
||||
public Guid Clsid;
|
||||
[MarshalAs(UnmanagedType.Struct)]
|
||||
public Guid FormatID;
|
||||
|
||||
public IntPtr CodecName = IntPtr.Zero;
|
||||
public IntPtr DllName = IntPtr.Zero;
|
||||
public IntPtr FormatDescription = IntPtr.Zero;
|
||||
public IntPtr FilenameExtension = IntPtr.Zero;
|
||||
public IntPtr MimeType = IntPtr.Zero;
|
||||
|
||||
public int Flags;
|
||||
public int Version;
|
||||
public int SigCount;
|
||||
public int SigSize;
|
||||
|
||||
public IntPtr SigPattern = IntPtr.Zero;
|
||||
public IntPtr SigMask = IntPtr.Zero;
|
||||
}
|
||||
|
||||
internal class ComStreamFromDataStream : IStream
|
||||
{
|
||||
protected Stream DataStream;
|
||||
|
||||
// to support seeking ahead of the stream length...
|
||||
private long _virtualPosition = -1;
|
||||
|
||||
internal ComStreamFromDataStream(Stream dataStream)
|
||||
{
|
||||
this.DataStream = dataStream ?? throw new ArgumentNullException(nameof(dataStream));
|
||||
}
|
||||
|
||||
private void ActualizeVirtualPosition()
|
||||
{
|
||||
if (_virtualPosition == -1) return;
|
||||
|
||||
if (_virtualPosition > DataStream.Length)
|
||||
DataStream.SetLength(_virtualPosition);
|
||||
|
||||
DataStream.Position = _virtualPosition;
|
||||
|
||||
_virtualPosition = -1;
|
||||
}
|
||||
|
||||
public virtual IStream Clone()
|
||||
{
|
||||
NotImplemented();
|
||||
return null!;
|
||||
}
|
||||
|
||||
public virtual void Commit(int grfCommitFlags)
|
||||
{
|
||||
DataStream.Flush();
|
||||
ActualizeVirtualPosition();
|
||||
}
|
||||
|
||||
public virtual long CopyTo(IStream pstm, long cb, long[] pcbRead)
|
||||
{
|
||||
const int bufsize = 4096; // one page
|
||||
var buffer = Marshal.AllocHGlobal(bufsize);
|
||||
if (buffer == IntPtr.Zero) throw new OutOfMemoryException();
|
||||
long written = 0;
|
||||
|
||||
try
|
||||
{
|
||||
while (written < cb)
|
||||
{
|
||||
var toRead = bufsize;
|
||||
if (written + toRead > cb) toRead = (int) (cb - written);
|
||||
var read = Read(buffer, toRead);
|
||||
if (read == 0) break;
|
||||
if (pstm.Write(buffer, read) != read)
|
||||
{
|
||||
throw EFail("Wrote an incorrect number of bytes");
|
||||
}
|
||||
written += read;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal(buffer);
|
||||
}
|
||||
if (pcbRead != null && pcbRead.Length > 0)
|
||||
{
|
||||
pcbRead[0] = written;
|
||||
}
|
||||
|
||||
return written;
|
||||
}
|
||||
|
||||
public virtual Stream GetDataStream() => DataStream;
|
||||
|
||||
public virtual void LockRegion(long libOffset, long cb, int dwLockType)
|
||||
{
|
||||
}
|
||||
|
||||
protected static ExternalException EFail(string msg) => throw new ExternalException(msg, E_FAIL);
|
||||
|
||||
protected static void NotImplemented() => throw new NotImplementedException();
|
||||
|
||||
public virtual int Read(IntPtr buf, int length)
|
||||
{
|
||||
var buffer = new byte[length];
|
||||
var count = Read(buffer, length);
|
||||
Marshal.Copy(buffer, 0, buf, length);
|
||||
return count;
|
||||
}
|
||||
|
||||
public virtual int Read(byte[] buffer, int length)
|
||||
{
|
||||
ActualizeVirtualPosition();
|
||||
return DataStream.Read(buffer, 0, length);
|
||||
}
|
||||
|
||||
public virtual void Revert() => NotImplemented();
|
||||
|
||||
public virtual long Seek(long offset, int origin)
|
||||
{
|
||||
var pos = _virtualPosition;
|
||||
if (_virtualPosition == -1)
|
||||
{
|
||||
pos = DataStream.Position;
|
||||
}
|
||||
var len = DataStream.Length;
|
||||
|
||||
switch (origin)
|
||||
{
|
||||
case StreamConsts.STREAM_SEEK_SET:
|
||||
if (offset <= len)
|
||||
{
|
||||
DataStream.Position = offset;
|
||||
_virtualPosition = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
_virtualPosition = offset;
|
||||
}
|
||||
break;
|
||||
case StreamConsts.STREAM_SEEK_END:
|
||||
if (offset <= 0)
|
||||
{
|
||||
DataStream.Position = len + offset;
|
||||
_virtualPosition = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
_virtualPosition = len + offset;
|
||||
}
|
||||
break;
|
||||
case StreamConsts.STREAM_SEEK_CUR:
|
||||
if (offset + pos <= len)
|
||||
{
|
||||
DataStream.Position = pos + offset;
|
||||
_virtualPosition = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
_virtualPosition = offset + pos;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return _virtualPosition != -1 ? _virtualPosition : DataStream.Position;
|
||||
}
|
||||
|
||||
public virtual void SetSize(long value) => DataStream.SetLength(value);
|
||||
|
||||
public virtual void Stat(IntPtr pstatstg, int grfStatFlag) => NotImplemented();
|
||||
|
||||
public virtual void UnlockRegion(long libOffset, long cb, int dwLockType)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual int Write(IntPtr buf, int length)
|
||||
{
|
||||
var buffer = new byte[length];
|
||||
Marshal.Copy(buf, buffer, 0, length);
|
||||
return Write(buffer, length);
|
||||
}
|
||||
|
||||
public virtual int Write(byte[] buffer, int length)
|
||||
{
|
||||
ActualizeVirtualPosition();
|
||||
DataStream.Write(buffer, 0, length);
|
||||
return length;
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal class MINMAXINFO
|
||||
{
|
||||
public POINT ptReserved;
|
||||
public POINT ptMaxSize;
|
||||
public POINT ptMaxPosition;
|
||||
public POINT ptMinTrackSize;
|
||||
public POINT ptMaxTrackSize;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct APPBARDATA
|
||||
{
|
||||
public int cbSize;
|
||||
public IntPtr hWnd;
|
||||
public uint uCallbackMessage;
|
||||
public uint uEdge;
|
||||
public RECT rc;
|
||||
public int lParam;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct RTL_OSVERSIONINFOEX
|
||||
{
|
||||
internal uint dwOSVersionInfoSize;
|
||||
internal uint dwMajorVersion;
|
||||
internal uint dwMinorVersion;
|
||||
internal uint dwBuildNumber;
|
||||
internal uint dwPlatformId;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
|
||||
internal string szCSDVersion;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1069:Enums values should not be duplicated", Justification = "<Pending>")]
|
||||
public enum WindowPositionFlags
|
||||
{
|
||||
SWP_ASYNCWINDOWPOS = 0x4000,
|
||||
SWP_DEFERERASE = 0x2000,
|
||||
SWP_DRAWFRAME = 0x0020,
|
||||
SWP_FRAMECHANGED = 0x0020,
|
||||
SWP_HIDEWINDOW = 0x0080,
|
||||
SWP_NOACTIVATE = 0x0010,
|
||||
SWP_NOCOPYBITS = 0x0100,
|
||||
SWP_NOMOVE = 0x0002,
|
||||
SWP_NOOWNERZORDER = 0x0200,
|
||||
SWP_NOREDRAW = 0x0008,
|
||||
SWP_NOREPOSITION = 0x0200,
|
||||
SWP_NOSENDCHANGING = 0x0400,
|
||||
SWP_NOSIZE = 0x0001,
|
||||
SWP_NOZORDER = 0x0004,
|
||||
SWP_SHOWWINDOW = 0x0040
|
||||
}
|
||||
}
|
||||
}
|
||||
78
src/MpvNet.Windows/WPF/HandyControl/Tools/RegexPatterns.cs
Normal file
78
src/MpvNet.Windows/WPF/HandyControl/Tools/RegexPatterns.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
|
||||
namespace HandyControl.Tools
|
||||
{
|
||||
public sealed class RegexPatterns
|
||||
{
|
||||
public const string MailPattern =
|
||||
@"^([\w-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$";
|
||||
|
||||
public const string PhonePattern = @"^((13[0-9])|(15[^4,\d])|(18[0,5-9]))\d{8}$";
|
||||
|
||||
public const string IpPattern =
|
||||
@"^(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\."
|
||||
+ @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\."
|
||||
+ @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\."
|
||||
+ @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)$";
|
||||
|
||||
public const string IpAPattern =
|
||||
@"^(12[0-6]|1[0-1]\d|[1-9]?\d)\."
|
||||
+ @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\."
|
||||
+ @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\."
|
||||
+ @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)$";
|
||||
|
||||
public const string IpBPattern =
|
||||
@"^(19[0-1]|12[8-9]|1[3-8]\d)\."
|
||||
+ @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\."
|
||||
+ @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\."
|
||||
+ @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)$";
|
||||
|
||||
public const string IpCPattern =
|
||||
@"^(19[2-9]|22[0-3]|2[0-1]\d)\."
|
||||
+ @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\."
|
||||
+ @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\."
|
||||
+ @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)$";
|
||||
|
||||
public const string IpDPattern =
|
||||
@"^(22[4-9]|23\d\."
|
||||
+ @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\."
|
||||
+ @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\."
|
||||
+ @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)$";
|
||||
|
||||
public const string IpEPattern =
|
||||
@"^(25[0-5]|24\d\."
|
||||
+ @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\."
|
||||
+ @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\."
|
||||
+ @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)$";
|
||||
|
||||
public const string ChinesePattern = @"^[\u4e00-\u9fa5]$";
|
||||
|
||||
public const string UrlPattern =
|
||||
@"((http|ftp|https)://)(([a-zA-Z0-9\._-]+\.[a-zA-Z]{2,6})|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,4})*(/[a-zA-Z0-9\&%_\./-~-]*)?";
|
||||
|
||||
public const string NumberPattern = @"^\d+$";
|
||||
|
||||
public const string DigitsPattern = @"[+-]?\d*\.?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?";
|
||||
|
||||
public const string PIntPattern = @"^[1-9]\d*$";
|
||||
|
||||
public const string NIntPattern = @"^-[1-9]\d*$ ";
|
||||
|
||||
public const string IntPattern = @"^-?[1-9]\d*|0$";
|
||||
|
||||
public const string NnIntPattern = @"^[1-9]\d*|0$";
|
||||
|
||||
public const string NpIntPattern = @"^-[1-9]\d*|0$";
|
||||
|
||||
public const string PDoublePattern = @"^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$";
|
||||
|
||||
public const string NDoublePattern = @"^-([1-9]\d*\.\d*|0\.\d*[1-9]\d*)$";
|
||||
|
||||
public const string DoublePattern = @"^-?([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0)$";
|
||||
|
||||
public const string NnDoublePattern = @"^[1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0$";
|
||||
|
||||
public const string NpDoublePattern = @"^(-([1-9]\d*\.\d*|0\.\d*[1-9]\d*))|0?\.0+|0$";
|
||||
|
||||
public object GetValue(string propertyName) => GetType().GetField(propertyName).GetValue(null);
|
||||
}
|
||||
}
|
||||
8
src/MpvNet.Windows/WPF/ISettingControl.cs
Normal file
8
src/MpvNet.Windows/WPF/ISettingControl.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
namespace MpvNet.Windows.WPF;
|
||||
|
||||
interface ISettingControl
|
||||
{
|
||||
bool Contains(string searchString);
|
||||
Setting Setting { get; }
|
||||
}
|
||||
128
src/MpvNet.Windows/WPF/InputWindow.xaml
Normal file
128
src/MpvNet.Windows/WPF/InputWindow.xaml
Normal file
@@ -0,0 +1,128 @@
|
||||
<Window
|
||||
x:Class="MpvNet.Windows.WPF.InputWindow"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:controls="clr-namespace:MpvNet.Windows.WPF.Controls"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
mc:Ignorable="d"
|
||||
|
||||
Title="Input Editor"
|
||||
Height="500"
|
||||
Width="800"
|
||||
FontSize="13"
|
||||
ShowInTaskbar="False"
|
||||
Foreground="{Binding Theme.Foreground}"
|
||||
Background="{Binding Theme.Background}"
|
||||
Loaded="Window_Loaded"
|
||||
Closed="Window_Closed"
|
||||
>
|
||||
|
||||
<Window.Resources>
|
||||
<Style x:Key="DataGridFontCentering" TargetType="{x:Type DataGridCell}">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type DataGridCell}">
|
||||
<Grid Background="{TemplateBinding Background}">
|
||||
<ContentPresenter VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="HeaderStyle" TargetType="DataGridColumnHeader">
|
||||
<Setter Property="Background" Value="{DynamicResource RegionBrush}" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource PrimaryTextBrush}" />
|
||||
<Setter Property="MinHeight" Value="22" />
|
||||
<Setter Property="Cursor" Value="Hand" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource PrimaryTextBrush}"/>
|
||||
<Setter Property="Padding" Value="1"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type Button}">
|
||||
<Border x:Name="border"
|
||||
BorderBrush="{DynamicResource PrimaryTextBrush}"
|
||||
BorderThickness="0"
|
||||
Background="{DynamicResource RegionBrush}">
|
||||
|
||||
<ContentPresenter
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
TextElement.FontWeight="Normal">
|
||||
</ContentPresenter>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" TargetName="border" Value="{DynamicResource HighlightBrush}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter Property="BorderBrush" TargetName="border" Value="#2C628B"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter Property="Opacity" TargetName="border" Value="0.25"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Window.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<controls:SearchControl
|
||||
HintText="Type ? to get help."
|
||||
x:Name="SearchControl"
|
||||
Width="300"
|
||||
Margin="0,20,0,20"
|
||||
Grid.ColumnSpan="2"
|
||||
/>
|
||||
|
||||
<DataGrid
|
||||
Name="DataGrid"
|
||||
Grid.Row="1"
|
||||
CommandManager.PreviewCanExecute="DataGrid_PreviewCanExecute"
|
||||
AutoGenerateColumns="False"
|
||||
ColumnHeaderStyle="{StaticResource HeaderStyle}"
|
||||
Foreground="{Binding Theme.Foreground}"
|
||||
Background="{Binding Theme.Background}"
|
||||
RowBackground="{Binding Theme.Background}"
|
||||
HorizontalGridLinesBrush="{Binding Theme.Foreground}"
|
||||
VerticalGridLinesBrush="{Binding Theme.Foreground}"
|
||||
CellStyle="{StaticResource DataGridFontCentering}"
|
||||
>
|
||||
|
||||
<DataGrid.Resources>
|
||||
<Style BasedOn="{StaticResource {x:Type DataGridColumnHeader}}" TargetType="{x:Type DataGridColumnHeader}">
|
||||
<Setter Property="Background" Value="{Binding DataContext.Theme.Background, ElementName=DataGrid}" />
|
||||
</Style>
|
||||
</DataGrid.Resources>
|
||||
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Name" Binding="{Binding Path}" MaxWidth="325"/>
|
||||
|
||||
<DataGridTemplateColumn Header="Input">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Button MinHeight="20" Click="ButtonClick">
|
||||
<TextBlock Text="{Binding Input}"></TextBlock>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTextColumn Header="Command" Binding="{Binding Command}" MaxWidth="325" />
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Grid>
|
||||
</Window>
|
||||
145
src/MpvNet.Windows/WPF/InputWindow.xaml.cs
Normal file
145
src/MpvNet.Windows/WPF/InputWindow.xaml.cs
Normal file
@@ -0,0 +1,145 @@
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using MpvNet.Windows.UI;
|
||||
|
||||
namespace MpvNet.Windows.WPF;
|
||||
|
||||
public partial class InputWindow : Window
|
||||
{
|
||||
ICollectionView CollectionView;
|
||||
string StartupContent;
|
||||
public List<Binding> Bindings { get; }
|
||||
public Theme? Theme => Theme.Current;
|
||||
|
||||
public InputWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = this;
|
||||
|
||||
if (App.InputConf.HasMenu)
|
||||
Bindings = InputHelp.Parse(App.InputConf.Content);
|
||||
else
|
||||
Bindings = InputHelp.GetEditorBindings(App.InputConf.Content);
|
||||
|
||||
StartupContent = InputHelp.ConvertToString(Bindings);
|
||||
SearchControl.SearchTextBox.TextChanged += SearchTextBox_TextChanged;
|
||||
DataGrid.SelectionMode = DataGridSelectionMode.Single;
|
||||
CollectionViewSource collectionViewSource = new CollectionViewSource() { Source = Bindings };
|
||||
CollectionView = collectionViewSource.View;
|
||||
CollectionView.Filter = new Predicate<object>(item => Filter((Binding)item));
|
||||
DataGrid.ItemsSource = CollectionView;
|
||||
}
|
||||
|
||||
void SearchTextBox_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
CollectionView.Refresh();
|
||||
|
||||
if (SearchControl.SearchTextBox.Text == "?")
|
||||
{
|
||||
SearchControl.SearchTextBox.Text = "";
|
||||
|
||||
Msg.ShowInfo("Filtering" + BR2 +
|
||||
"Reduce the filter scope with:" + BR2 +
|
||||
"i input" + BR2 +
|
||||
"m menu" + BR2 +
|
||||
"c command" + BR2 +
|
||||
"If only one character is entered input search is performed.");
|
||||
}
|
||||
}
|
||||
|
||||
bool Filter(Binding item)
|
||||
{
|
||||
if (item.Command == "")
|
||||
return false;
|
||||
|
||||
string searchText = SearchControl.SearchTextBox.Text.ToLower();
|
||||
|
||||
if (searchText == "" || searchText == "?")
|
||||
return true;
|
||||
|
||||
if (searchText.Length == 1)
|
||||
return item.Input.ToLower().Replace("ctrl+", "").Replace("shift+", "").Replace("alt+", "") == searchText.ToLower();
|
||||
else if (searchText.StartsWith("i ") || searchText.StartsWith("i:") || searchText.Length == 1)
|
||||
{
|
||||
if (searchText.Length > 1)
|
||||
searchText = searchText.Substring(2).Trim();
|
||||
|
||||
if (searchText.Length < 3)
|
||||
return item.Input.ToLower().Replace("ctrl+", "").Replace("shift+", "").Replace("alt+", "").Contains(searchText);
|
||||
else
|
||||
return item.Input.ToLower().Contains(searchText);
|
||||
}
|
||||
else if (searchText.StartsWith("m ") || searchText.StartsWith("m:"))
|
||||
return item.Path.ToLower().Contains(searchText.Substring(2).Trim());
|
||||
else if (searchText.StartsWith("c ") || searchText.StartsWith("c:"))
|
||||
return item.Command.ToLower().Contains(searchText.Substring(2).Trim());
|
||||
else if (item.Command.ToLower().Contains(searchText) ||
|
||||
item.Path.ToLower().Contains(searchText) ||
|
||||
item.Input.ToLower().Contains(searchText))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ButtonClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Binding? item = ((Button)e.Source).DataContext as Binding;
|
||||
|
||||
if (item == null)
|
||||
return;
|
||||
|
||||
LearnWindow window = new LearnWindow();
|
||||
window.Owner = this;
|
||||
window.InputItem = item;
|
||||
window.ShowDialog();
|
||||
Keyboard.Focus(SearchControl.SearchTextBox);
|
||||
}
|
||||
|
||||
void Window_Loaded(object sender, RoutedEventArgs e) => Keyboard.Focus(SearchControl.SearchTextBox);
|
||||
|
||||
void Window_Closed(object sender, EventArgs e)
|
||||
{
|
||||
string newContent = InputHelp.ConvertToString(Bindings);
|
||||
|
||||
if (StartupContent == newContent)
|
||||
return;
|
||||
|
||||
if (App.InputConf.HasMenu)
|
||||
File.WriteAllText(App.InputConf.Path, App.InputConf.Content = newContent);
|
||||
else
|
||||
{
|
||||
newContent = InputHelp.ConvertToString(InputHelp.GetReducedBindings(Bindings));
|
||||
File.WriteAllText(App.InputConf.Path, App.InputConf.Content = newContent);
|
||||
}
|
||||
|
||||
Msg.ShowInfo("Changes will be available on next startup.");
|
||||
}
|
||||
|
||||
void DataGrid_PreviewCanExecute(object sender, CanExecuteRoutedEventArgs e)
|
||||
{
|
||||
DataGrid grid = (DataGrid)sender;
|
||||
|
||||
if (e.Command == DataGrid.DeleteCommand)
|
||||
if (Msg.ShowQuestion($"Confirm to delete: {(grid.SelectedItem as Binding)!.Input} ({(grid.SelectedItem as Binding)!.Path})") != MessageBoxResult.OK)
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
protected override void OnKeyDown(KeyEventArgs e)
|
||||
{
|
||||
base.OnKeyDown(e);
|
||||
|
||||
if (e.Key == Key.Escape)
|
||||
Close();
|
||||
|
||||
if (e.Key == Key.F3 || e.Key == Key.F6 || (e.Key == Key.F && Keyboard.IsKeyDown(Key.LeftCtrl)))
|
||||
{
|
||||
Keyboard.Focus(SearchControl.SearchTextBox);
|
||||
SearchControl.SearchTextBox.SelectAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
54
src/MpvNet.Windows/WPF/LearnWindow.xaml
Normal file
54
src/MpvNet.Windows/WPF/LearnWindow.xaml
Normal file
@@ -0,0 +1,54 @@
|
||||
<Window
|
||||
x:Class="MpvNet.Windows.LearnWindow"
|
||||
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"
|
||||
mc:Ignorable="d"
|
||||
|
||||
Title="Learn Input"
|
||||
Height="150"
|
||||
Width="350"
|
||||
FontSize="16"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
ResizeMode="NoResize"
|
||||
Loaded="Window_Loaded"
|
||||
Foreground="{Binding Theme.Foreground}"
|
||||
Background="{Binding Theme.Background}"
|
||||
MouseWheel="Window_MouseWheel"
|
||||
MouseUp="Window_MouseUp"
|
||||
MouseDoubleClick="Window_MouseDoubleClick" PreviewKeyDown="Window_PreviewKeyDown"
|
||||
>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="40" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock
|
||||
x:Name="MenuTextBlock"
|
||||
Margin="0,10,0,0"
|
||||
Grid.ColumnSpan="2"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
/>
|
||||
|
||||
<TextBlock
|
||||
x:Name="KeyTextBlock"
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="2"
|
||||
VerticalAlignment="Top"
|
||||
HorizontalAlignment="Center"
|
||||
/>
|
||||
|
||||
<Button x:Name="ConfirmButton" Grid.Row="2" Margin="10,0,10,10" Click="ConfirmButton_Click">Confirm</Button>
|
||||
<Button x:Name="ClearButton" Grid.Row="2" Margin="0,0,10,10" Click="ClearButton_Click" Grid.Column="1">Clear</Button>
|
||||
</Grid>
|
||||
</Window>
|
||||
272
src/MpvNet.Windows/WPF/LearnWindow.xaml.cs
Normal file
272
src/MpvNet.Windows/WPF/LearnWindow.xaml.cs
Normal file
@@ -0,0 +1,272 @@
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using MpvNet.Help;
|
||||
using MpvNet.Windows.UI;
|
||||
|
||||
namespace MpvNet.Windows;
|
||||
|
||||
public partial class LearnWindow : Window
|
||||
{
|
||||
public Binding? InputItem { get; set; }
|
||||
string NewKey = "";
|
||||
|
||||
uint MAPVK_VK_TO_VSC = 0;
|
||||
|
||||
int VK_MENU = 0x12;
|
||||
int VK_LMENU = 0xA4;
|
||||
int VK_RMENU = 0xA5;
|
||||
|
||||
int VK_CONTROL = 0x11;
|
||||
int VK_LCONTROL = 0xA2;
|
||||
int VK_RCONTROL = 0xA3;
|
||||
|
||||
public LearnWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = this;
|
||||
}
|
||||
|
||||
public Theme? Theme => Theme.Current;
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
||||
static extern short GetKeyState(int keyCode);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
static extern uint MapVirtualKey(uint uCode, uint uMapType);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
||||
static extern int ToUnicode(uint wVirtKey, uint wScanCode, byte[] lpKeyState,
|
||||
StringBuilder pwszBuff, int cchBuff, uint wFlags);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
static extern bool GetKeyboardState(byte[] lpKeyState);
|
||||
|
||||
string ToUnicode(uint vk)
|
||||
{
|
||||
byte[] keys = new byte[256];
|
||||
|
||||
if (!GetKeyboardState(keys))
|
||||
return "";
|
||||
|
||||
if ((keys[VK_CONTROL] & 0x80) != 0 && (keys[VK_MENU] & 0x80) == 0)
|
||||
keys[VK_LCONTROL] = keys[VK_RCONTROL] = keys[VK_CONTROL] = 0;
|
||||
|
||||
uint scanCode = MapVirtualKey(vk, MAPVK_VK_TO_VSC);
|
||||
|
||||
string ret = ToUnicode(vk, scanCode, keys);
|
||||
|
||||
if (ret.Length == 1 && ret[0] < 32)
|
||||
return "";
|
||||
|
||||
if (ret == "" && (keys[VK_MENU] & 0x80) != 0)
|
||||
{
|
||||
keys[VK_LMENU] = keys[VK_RMENU] = keys[VK_MENU] = 0;
|
||||
ret = ToUnicode(vk, scanCode, keys);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public string ToUnicode(uint vk, uint scanCode, byte[] keys)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder(10);
|
||||
ToUnicode(vk, scanCode, keys, sb, sb.Capacity, 0);
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
|
||||
{
|
||||
Message m = new Message();
|
||||
m.HWnd = hwnd;
|
||||
m.Msg = msg;
|
||||
m.WParam = wParam;
|
||||
m.LParam = lParam;
|
||||
ProcessKeyEventArgs(ref m);
|
||||
return m.Result;
|
||||
}
|
||||
|
||||
void OnKeyDown(uint vk)
|
||||
{
|
||||
Keys key = (Keys)vk;
|
||||
|
||||
if (key == Keys.ControlKey || key == Keys.ShiftKey ||
|
||||
key == Keys.Menu || key == Keys.None)
|
||||
|
||||
return;
|
||||
|
||||
string text = ToUnicode(vk);
|
||||
|
||||
if ((int)key > 111 && (int)key < 136)
|
||||
text = "F" + ((int)key - 111);
|
||||
|
||||
if ((int)key > 95 && (int)key < 106)
|
||||
text = "KP" + ((int)key - 96);
|
||||
|
||||
switch (text)
|
||||
{
|
||||
case "#": text = "SHARP"; break;
|
||||
case "´´": text = "´"; break;
|
||||
case "``": text = "`"; break;
|
||||
case "^^": text = "^"; break;
|
||||
}
|
||||
|
||||
switch (key)
|
||||
{
|
||||
case Keys.Left: text = "Left"; break;
|
||||
case Keys.Up: text = "Up"; break;
|
||||
case Keys.Right: text = "Right"; break;
|
||||
case Keys.Down: text = "Down"; break;
|
||||
case Keys.Space: text = "Space"; break;
|
||||
case Keys.Enter: text = "Enter"; break;
|
||||
case Keys.Tab: text = "Tab"; break;
|
||||
case Keys.Back: text = "BS"; break;
|
||||
case Keys.Delete: text = "Del"; break;
|
||||
case Keys.Insert: text = "Ins"; break;
|
||||
case Keys.Home: text = "Home"; break;
|
||||
case Keys.End: text = "End"; break;
|
||||
case Keys.PageUp: text = "PGUP"; break;
|
||||
case Keys.PageDown: text = "PGDWN"; break;
|
||||
case Keys.Escape: text = "Esc"; break;
|
||||
case Keys.Sleep: text = "Sleep"; break;
|
||||
case Keys.Cancel: text = "Cancel"; break;
|
||||
case Keys.PrintScreen: text = "Print"; break;
|
||||
case Keys.BrowserFavorites: text = "Favorites"; break;
|
||||
case Keys.BrowserSearch: text = "Search"; break;
|
||||
case Keys.BrowserHome: text = "Homepage"; break;
|
||||
case Keys.LaunchMail: text = "Mail"; break;
|
||||
case Keys.Play: text = "Play"; break;
|
||||
case Keys.Pause: text = "Pause"; break;
|
||||
case Keys.MediaPlayPause: text = "PlayPause"; break;
|
||||
case Keys.MediaStop: text = "Stop"; break;
|
||||
case Keys.MediaNextTrack: text = "Next"; break;
|
||||
case Keys.MediaPreviousTrack: text = "Prev"; break;
|
||||
|
||||
case Keys.VolumeUp:
|
||||
case Keys.VolumeDown:
|
||||
case Keys.VolumeMute:
|
||||
text = ""; break;
|
||||
}
|
||||
|
||||
bool isAlt = GetKeyState(18) < 0;
|
||||
bool isShift = GetKeyState(16) < 0;
|
||||
bool isCtrl = GetKeyState(17) < 0;
|
||||
|
||||
bool isLetter = (int)key > 64 && (int)key < 91;
|
||||
|
||||
if (isLetter && isShift)
|
||||
text = text.ToUpper();
|
||||
|
||||
string keyString = ToUnicode(vk);
|
||||
|
||||
if (isAlt && !isCtrl)
|
||||
text = "ALT+" + text;
|
||||
|
||||
if (isShift && keyString == "")
|
||||
text = "SHIFT+" + text;
|
||||
|
||||
if (isCtrl && !(keyString != "" && isCtrl && isAlt))
|
||||
text = "CTRL+" + text;
|
||||
|
||||
if (!string.IsNullOrEmpty(text))
|
||||
SetKey(text);
|
||||
}
|
||||
|
||||
void SetKey(string? key)
|
||||
{
|
||||
NewKey = key!;
|
||||
MenuTextBlock.Text = InputItem?.Path;
|
||||
KeyTextBlock.Text = key;
|
||||
}
|
||||
|
||||
void ProcessKeyEventArgs(ref Message m)
|
||||
{
|
||||
int WM_KEYDOWN = 0x100;
|
||||
int WM_SYSKEYDOWN = 0x104;
|
||||
int WM_APPCOMMAND = 0x319;
|
||||
|
||||
if (m.Msg == WM_KEYDOWN || m.Msg == WM_SYSKEYDOWN)
|
||||
OnKeyDown((uint)m.WParam.ToInt64());
|
||||
else if (m.Msg == WM_APPCOMMAND)
|
||||
{
|
||||
string? value = MpvHelp.WM_APPCOMMAND_to_mpv_key((int)(m.LParam.ToInt64() >> 16 & ~0xf000));
|
||||
|
||||
if (value != null)
|
||||
SetKey(value);
|
||||
}
|
||||
}
|
||||
|
||||
void Window_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
HwndSource source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
|
||||
source.AddHook(new HwndSourceHook(WndProc));
|
||||
SetKey(InputItem?.Input);
|
||||
}
|
||||
|
||||
void ConfirmButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
InputItem!.Input = NewKey;
|
||||
Close();
|
||||
}
|
||||
|
||||
void ClearButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
InputItem!.Input = "";
|
||||
Close();
|
||||
}
|
||||
|
||||
void Window_MouseWheel(object sender, MouseWheelEventArgs e)
|
||||
{
|
||||
if (e.Delta > 0)
|
||||
SetKey("WHEEL_UP");
|
||||
else
|
||||
SetKey("WHEEL_DOWN");
|
||||
}
|
||||
|
||||
void Window_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
switch (e.ChangedButton)
|
||||
{
|
||||
case MouseButton.Left:
|
||||
if (BlockMBTN_LEFT)
|
||||
BlockMBTN_LEFT = false;
|
||||
else
|
||||
SetKey("MBTN_LEFT");
|
||||
break;
|
||||
case MouseButton.Middle:
|
||||
SetKey("MBTN_MID");
|
||||
break;
|
||||
case MouseButton.XButton1:
|
||||
SetKey("MBTN_BACK");
|
||||
break;
|
||||
case MouseButton.XButton2:
|
||||
SetKey("MBTN_FORWARD");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool BlockMBTN_LEFT;
|
||||
|
||||
void Window_MouseDoubleClick(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.ChangedButton == MouseButton.Left)
|
||||
{
|
||||
SetKey("MBTN_LEFT_DBL");
|
||||
BlockMBTN_LEFT = true;
|
||||
}
|
||||
}
|
||||
|
||||
void Window_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Tab)
|
||||
{
|
||||
OnKeyDown((uint)Keys.Tab);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
53
src/MpvNet.Windows/WPF/MenuHelp.cs
Normal file
53
src/MpvNet.Windows/WPF/MenuHelp.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace MpvNet.Windows.WPF;
|
||||
|
||||
public class MenuHelp
|
||||
{
|
||||
public static MenuItem? Add(ItemCollection? items, string path)
|
||||
{
|
||||
string[] parts = path.Split(new[] { " > ", " | " }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
for (int x = 0; x < parts.Length; x++)
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
foreach (MenuItem i in items!.OfType<MenuItem>())
|
||||
{
|
||||
if (x < parts.Length - 1)
|
||||
{
|
||||
if ((string)i.Header == parts[x])
|
||||
{
|
||||
found = true;
|
||||
items = i.Items;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
if (x == parts.Length - 1)
|
||||
{
|
||||
if (parts[x] == "-")
|
||||
items?.Add(new Separator());
|
||||
else
|
||||
{
|
||||
MenuItem item = new MenuItem() { Header = parts[x] };
|
||||
items?.Add(item);
|
||||
items = item.Items;
|
||||
return item;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MenuItem item = new MenuItem() { Header = parts[x] };
|
||||
items?.Add(item);
|
||||
items = item.Items;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
51
src/MpvNet.Windows/WPF/Msg.cs
Normal file
51
src/MpvNet.Windows/WPF/Msg.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
|
||||
using Forms = System.Windows.Forms;
|
||||
|
||||
using MpvNet.Windows.WPF.MsgBox;
|
||||
|
||||
namespace MpvNet.Windows.WPF;
|
||||
|
||||
public class Msg
|
||||
{
|
||||
public static void ShowInfo(object msg) => Show(msg, MessageBoxImage.Information);
|
||||
|
||||
public static void ShowError(object msg) => Show(msg, MessageBoxImage.Error);
|
||||
|
||||
public static void ShowWarning(object msg) => Show(msg, MessageBoxImage.Warning);
|
||||
|
||||
public static MessageBoxResult ShowQuestion(object msg,
|
||||
MessageBoxButton buttons = MessageBoxButton.OKCancel)
|
||||
{
|
||||
return Show(msg, MessageBoxImage.Question, buttons);
|
||||
}
|
||||
|
||||
public static void ShowException(Exception exception)
|
||||
{
|
||||
Show(exception.Message, MessageBoxImage.Error, MessageBoxButton.OK, exception.ToString());
|
||||
}
|
||||
|
||||
public static MessageBoxResult Show(
|
||||
object msg,
|
||||
MessageBoxImage img,
|
||||
MessageBoxButton buttons = MessageBoxButton.OK,
|
||||
string? details = null)
|
||||
{
|
||||
ApartmentState state = Thread.CurrentThread.GetApartmentState();
|
||||
|
||||
if (state == ApartmentState.STA)
|
||||
return fn();
|
||||
else
|
||||
return Application.Current.Dispatcher.Invoke(fn);
|
||||
|
||||
MessageBoxResult fn()
|
||||
{
|
||||
MessageBoxEx.DetailsText = details;
|
||||
|
||||
return MessageBoxEx.OpenMessageBox((msg + "").Trim(),
|
||||
Forms.Application.ProductName, buttons, img);
|
||||
}
|
||||
}
|
||||
}
|
||||
226
src/MpvNet.Windows/WPF/MsgBox/MessageBoxEx.xaml
Normal file
226
src/MpvNet.Windows/WPF/MsgBox/MessageBoxEx.xaml
Normal file
@@ -0,0 +1,226 @@
|
||||
<Window x:Class="MpvNet.Windows.WPF.MsgBox.MessageBoxEx"
|
||||
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"
|
||||
mc:Ignorable="d"
|
||||
|
||||
Title="{Binding Path=MessageTitle}"
|
||||
MinHeight="100"
|
||||
MaxHeight="650"
|
||||
MinWidth="200"
|
||||
MaxWidth="{Binding Path=MaxFormWidth}"
|
||||
Background="{Binding Path=MessageBackground}"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
ShowInTaskbar="False"
|
||||
WindowStyle="SingleBorderWindow"
|
||||
ResizeMode="NoResize"
|
||||
SizeToContent="WidthAndHeight"
|
||||
SizeChanged="NotifiableWindow_SizeChanged"
|
||||
Loaded="Window_Loaded"
|
||||
Closing="Window_Closing"
|
||||
Height="Auto"
|
||||
Width="Auto"
|
||||
SourceInitialized="Window_SourceInitialized"
|
||||
>
|
||||
|
||||
<Window.Resources>
|
||||
<Style x:Key="GroupBoxExpanderToggleButtonStyle" TargetType="{x:Type ToggleButton}">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type ToggleButton}">
|
||||
<Grid SnapsToDevicePixels="False" Background="Transparent">
|
||||
<Ellipse HorizontalAlignment="Center" x:Name="circle" VerticalAlignment="Center" Width="15" Height="15"
|
||||
Fill="{DynamicResource ButtonNormalBackgroundFill}" Stroke="{Binding Path=MessageForeground}"/>
|
||||
<Ellipse Visibility="Hidden" HorizontalAlignment="Center" x:Name="shadow" VerticalAlignment="Center" Width="13" Height="13"
|
||||
Fill="{DynamicResource ExpanderShadowFill}"/>
|
||||
<Path SnapsToDevicePixels="false" x:Name="arrow" VerticalAlignment="Center" HorizontalAlignment="Center"
|
||||
Stroke="{Binding Path=MessageForeground}" StrokeThickness="2" Data="M1,1 L4,4 7,1" />
|
||||
</Grid>
|
||||
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsChecked" Value="true">
|
||||
<Setter Property="Data" TargetName="arrow" Value="M 1,4 L 4,1 L 7,4"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<SolidColorBrush x:Key="GroupBoxBorderBrush" Color="#D0D0BF"/>
|
||||
<SolidColorBrush x:Key="GroupBoxHeaderBrush" Color="White"/>
|
||||
<BorderGapMaskConverter x:Key="BorderGapMaskConverter"/>
|
||||
|
||||
<Style x:Key="linkCursor" TargetType="{x:Type TextBlock}" >
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True" >
|
||||
<Setter Property="Cursor" Value="Hand" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
</Window.Resources>
|
||||
|
||||
<Grid >
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid x:Name="gridMsg" Grid.Row="0" Margin="10,10,0,10" >
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid x:Name="imgGrid" Grid.Column="0">
|
||||
<Image x:Name="imgMsgBoxIcon"
|
||||
Source="{Binding Path=MessageIcon}"
|
||||
Visibility="{Binding Path=ShowIcon}"
|
||||
Width="32" Height="32"
|
||||
Margin="0,0,10,0"
|
||||
VerticalAlignment="Top"
|
||||
MouseLeftButtonUp="ImgMsgBoxIcon_MouseLeftButtonUp"
|
||||
>
|
||||
</Image>
|
||||
</Grid>
|
||||
<Grid Grid.Column="1" VerticalAlignment="Center" >
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBox Grid.Row="0" x:Name="textboxMessage"
|
||||
TextWrapping="Wrap" BorderThickness="0" IsReadOnly="True"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||
Padding="0,0,10,0"
|
||||
Background="Transparent"
|
||||
Foreground="{Binding Path=MessageForeground}"
|
||||
Text="{Binding Path=Message}" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<TextBlock Grid.Row="1" x:Name="tbUrl" Margin="0,0,0,10"
|
||||
Visibility="{Binding Path=ShowUrl}"
|
||||
Foreground="{Binding Path=UrlForeground}"
|
||||
TextWrapping="Wrap"
|
||||
TextDecorations="Underline"
|
||||
HorizontalAlignment="Center"
|
||||
MouseLeftButtonUp="TbUrl_MouseLeftButtonUp"
|
||||
Style="{StaticResource linkCursor}"
|
||||
/>
|
||||
|
||||
<CheckBox Grid.Row="2" Margin="0,0,0,10" HorizontalAlignment="Center"
|
||||
Visibility="{Binding Path=ShowCheckBox}"
|
||||
Content="{Binding Path=CheckBoxData.CheckBoxText}"
|
||||
IsChecked="{Binding Path=CheckBoxData.CheckBoxIsChecked}"
|
||||
Foreground="{Binding Path=MessageForeground}"></CheckBox>
|
||||
|
||||
<Border x:Name="stackButtons" Grid.Row="3" Padding="10"
|
||||
Background="{Binding Path=ButtonBackground}" >
|
||||
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center" >
|
||||
<Button x:Name="btnOK" Content=" OK " MinWidth="75" Margin="5,0,5,0"
|
||||
Width="{Binding Path=ButtonWidth}"
|
||||
Height="25"
|
||||
Visibility="{Binding Path=ShowOk}"
|
||||
IsDefault="{Binding Path=IsDefaultOK}"
|
||||
Click="BtnOK_Click" />
|
||||
<Button x:Name="btnYes" Content=" Yes " MinWidth="75" Margin="5,0,5,0"
|
||||
Width="{Binding Path=ButtonWidth}"
|
||||
Visibility="{Binding Path=ShowYes}"
|
||||
IsDefault="{Binding Path=IsDefaultYes}"
|
||||
Click="BtnYes_Click"/>
|
||||
<Button x:Name="btnNo" Content=" No " MinWidth="75" Margin="5,0,5,0"
|
||||
Width="{Binding Path=ButtonWidth}"
|
||||
Visibility="{Binding Path=ShowNo}"
|
||||
IsDefault="{Binding Path=IsDefaultNo}"
|
||||
Click="BtnNo_Click"/>
|
||||
<Button x:Name="btnAbort" Content=" Abort " MinWidth="75" Margin="5,0,5,0"
|
||||
Width="{Binding Path=ButtonWidth}"
|
||||
Visibility="{Binding Path=ShowAbort}"
|
||||
IsDefault="{Binding Path=IsDefaultAbort}"
|
||||
Click="BtnAbort_Click"/>
|
||||
<Button x:Name="btnRetry" Content=" Retry " MinWidth="75" Margin="5,0,5,0"
|
||||
Width="{Binding Path=ButtonWidth}"
|
||||
Visibility="{Binding Path=ShowRetry}"
|
||||
IsDefault="{Binding Path=IsDefaultRetry}"
|
||||
Click="BtnRetry_Click"/>
|
||||
<Button x:Name="btnIgnore" Content=" Ignore " MinWidth="75" Margin="5,0,5,0"
|
||||
Width="{Binding Path=ButtonWidth}"
|
||||
Visibility="{Binding Path=ShowIgnore}"
|
||||
IsDefault="{Binding Path=IsDefaultIgnore}"
|
||||
Click="BtnIgnore_Click"/>
|
||||
<Button x:Name="btnCancel" Content=" Cancel " MinWidth="75" Margin="5,0,5,0"
|
||||
Width="{Binding Path=ButtonWidth}"
|
||||
Visibility="{Binding Path=ShowCancel}"
|
||||
IsDefault="{Binding Path=IsDefaultCancel}"
|
||||
Click="BtnCancel_Click"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Expander Grid.Row="4" Header=" Details"
|
||||
IsExpanded="{Binding Path=Expanded}"
|
||||
Margin="3,8,0,0"
|
||||
Visibility="{Binding Path=ShowDetailsBtn}"
|
||||
>
|
||||
<Expander.Template>
|
||||
<ControlTemplate TargetType="{x:Type Expander}">
|
||||
<Grid SnapsToDevicePixels="true">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="6" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="6" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="6" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid SnapsToDevicePixels="False" Background="Transparent" Grid.Column="1" Grid.Row="0" Grid.RowSpan="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ToggleButton Grid.Column="0" MinHeight="0" MinWidth="0" Name="HeaderToggle"
|
||||
Style="{StaticResource GroupBoxExpanderToggleButtonStyle}"
|
||||
IsChecked="{Binding Path=IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" />
|
||||
<ContentPresenter ContentSource="Header" RecognizesAccessKey="true"
|
||||
TextElement.Foreground="{Binding ElementName=textboxMessage, Path=Foreground}"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
|
||||
Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Left"
|
||||
/>
|
||||
</Grid>
|
||||
<ContentPresenter x:Name="ExpandSite" Visibility="Collapsed" Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2"
|
||||
Margin="{TemplateBinding Padding}"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsExpanded" Value="true">
|
||||
<Setter Property="Visibility" TargetName="ExpandSite" Value="Visible"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="false">
|
||||
<Setter Property="Foreground" Value="{Binding Path=MessageForeground}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="true">
|
||||
<Setter Property="Foreground" Value="{Binding Path=MessageForeground}" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Expander.Template>
|
||||
<TextBox x:Name="textboxDetails" Grid.Row="2" Margin="0,10,0,0" Text="{Binding Path=DetailsText}" Height="100"
|
||||
TextWrapping="Wrap" BorderThickness="0" IsReadOnly="True"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||
Padding="0,0,10,0"
|
||||
Background="Transparent"
|
||||
Foreground="{Binding Path=MessageForeground}"
|
||||
/>
|
||||
</Expander>
|
||||
|
||||
</Grid>
|
||||
</Window>
|
||||
893
src/MpvNet.Windows/WPF/MsgBox/MessageBoxEx.xaml.cs
Normal file
893
src/MpvNet.Windows/WPF/MsgBox/MessageBoxEx.xaml.cs
Normal file
@@ -0,0 +1,893 @@
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using System.Media;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
using MpvNet.Help;
|
||||
|
||||
namespace MpvNet.Windows.WPF.MsgBox;
|
||||
|
||||
public partial class MessageBoxEx : Window, INotifyPropertyChanged
|
||||
{
|
||||
#region INotifyPropertyChanged
|
||||
|
||||
private bool isModified = false;
|
||||
|
||||
public bool IsModified {
|
||||
get { return isModified; }
|
||||
set {
|
||||
if (value != isModified)
|
||||
{
|
||||
isModified = true;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
|
||||
{
|
||||
if (PropertyChanged != null)
|
||||
{
|
||||
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
|
||||
|
||||
if (propertyName != "IsModified")
|
||||
IsModified = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion INotifyPropertyChanged
|
||||
|
||||
#region fields
|
||||
|
||||
private string? message;
|
||||
private string? messageTitle;
|
||||
private MessageBoxButton? buttons;
|
||||
private MessageBoxResult messageResult;
|
||||
private MessageBoxButtonEx? buttonsEx;
|
||||
private MessageBoxResultEx messageResultEx;
|
||||
private ImageSource? messageIcon;
|
||||
private MessageBoxImage msgBoxImage;
|
||||
private double buttonWidth = 0d;
|
||||
private bool expanded = false;
|
||||
private bool isDefaultOK;
|
||||
private bool isDefaultCancel;
|
||||
private bool isDefaultYes;
|
||||
private bool isDefaultNo;
|
||||
private bool isDefaultAbort;
|
||||
private bool isDefaultRetry;
|
||||
private bool isDefaultIgnore;
|
||||
|
||||
private bool usingExButtons = false;
|
||||
|
||||
#endregion fields
|
||||
|
||||
#region properties
|
||||
|
||||
public string Message {
|
||||
get { return message; }
|
||||
set {
|
||||
if (value != message)
|
||||
{
|
||||
message = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string MessageTitle {
|
||||
get { return messageTitle; }
|
||||
set {
|
||||
if (value != messageTitle)
|
||||
{
|
||||
messageTitle = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public MessageBoxResult MessageResult { get { return this.messageResult; } set { this.messageResult = value; } }
|
||||
public MessageBoxResultEx MessageResultEx { get { return this.messageResultEx; } set { this.messageResultEx = value; } }
|
||||
|
||||
public MessageBoxButton? Buttons {
|
||||
get { return buttons; }
|
||||
set {
|
||||
if (value != buttons)
|
||||
{
|
||||
buttons = value;
|
||||
NotifyPropertyChanged();
|
||||
NotifyPropertyChanged("ShowOk");
|
||||
NotifyPropertyChanged("ShowCancel");
|
||||
NotifyPropertyChanged("ShowYes");
|
||||
NotifyPropertyChanged("ShowNo");
|
||||
}
|
||||
}
|
||||
}
|
||||
public MessageBoxButtonEx? ButtonsEx {
|
||||
get { return buttonsEx; }
|
||||
set {
|
||||
if (value != buttonsEx)
|
||||
{
|
||||
buttonsEx = value;
|
||||
NotifyPropertyChanged();
|
||||
NotifyPropertyChanged("ShowOk");
|
||||
NotifyPropertyChanged("ShowCancel");
|
||||
NotifyPropertyChanged("ShowYes");
|
||||
NotifyPropertyChanged("ShowNo");
|
||||
NotifyPropertyChanged("ShowAbort");
|
||||
NotifyPropertyChanged("ShowRetry");
|
||||
NotifyPropertyChanged("ShowIgnore");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Visibility ShowOk => (!usingExButtons && Buttons == MessageBoxButton.OK ||
|
||||
!usingExButtons && Buttons == MessageBoxButton.OKCancel ||
|
||||
usingExButtons && ButtonsEx == MessageBoxButtonEx.OK ||
|
||||
usingExButtons && ButtonsEx == MessageBoxButtonEx.OKCancel) ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
public Visibility ShowCancel => (!usingExButtons && Buttons == MessageBoxButton.OKCancel ||
|
||||
!usingExButtons && Buttons == MessageBoxButton.YesNoCancel ||
|
||||
usingExButtons && ButtonsEx == MessageBoxButtonEx.OKCancel ||
|
||||
usingExButtons && ButtonsEx == MessageBoxButtonEx.YesNoCancel ||
|
||||
usingExButtons && ButtonsEx == MessageBoxButtonEx.RetryCancel) ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
public Visibility ShowYes => (!usingExButtons && Buttons == MessageBoxButton.YesNo ||
|
||||
!usingExButtons && Buttons == MessageBoxButton.YesNoCancel ||
|
||||
usingExButtons && ButtonsEx == MessageBoxButtonEx.YesNo ||
|
||||
usingExButtons && ButtonsEx == MessageBoxButtonEx.YesNoCancel) ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
public Visibility ShowNo => (!usingExButtons && Buttons == MessageBoxButton.YesNo ||
|
||||
!usingExButtons && Buttons == MessageBoxButton.YesNoCancel ||
|
||||
usingExButtons && ButtonsEx == MessageBoxButtonEx.YesNo ||
|
||||
usingExButtons && ButtonsEx == MessageBoxButtonEx.YesNoCancel) ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
public Visibility ShowRetry => (usingExButtons && ButtonsEx == MessageBoxButtonEx.AbortRetryIgnore ||
|
||||
usingExButtons && ButtonsEx == MessageBoxButtonEx.RetryCancel) ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
public Visibility ShowAbort => (usingExButtons && ButtonsEx == MessageBoxButtonEx.AbortRetryIgnore)
|
||||
? Visibility.Visible
|
||||
: Visibility.Collapsed;
|
||||
|
||||
public Visibility ShowIgnore => (usingExButtons && ButtonsEx == MessageBoxButtonEx.AbortRetryIgnore)
|
||||
? Visibility.Visible
|
||||
: Visibility.Collapsed;
|
||||
|
||||
public Visibility ShowIcon => (MessageIcon != null) ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
public ImageSource? MessageIcon {
|
||||
get => messageIcon;
|
||||
set {
|
||||
if (value != messageIcon)
|
||||
{
|
||||
messageIcon = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public double ButtonWidth {
|
||||
get => buttonWidth;
|
||||
set {
|
||||
if (value != buttonWidth)
|
||||
{
|
||||
buttonWidth = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Expanded {
|
||||
get => expanded;
|
||||
set {
|
||||
if (value != expanded)
|
||||
{
|
||||
expanded = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDefaultOK {
|
||||
get => isDefaultOK;
|
||||
set {
|
||||
if (value != isDefaultOK)
|
||||
{
|
||||
isDefaultOK = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDefaultCancel {
|
||||
get => isDefaultCancel;
|
||||
set {
|
||||
if (value != isDefaultCancel)
|
||||
{
|
||||
isDefaultCancel = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDefaultYes {
|
||||
get => isDefaultYes;
|
||||
set {
|
||||
if (value != isDefaultYes)
|
||||
{
|
||||
isDefaultYes = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDefaultNo {
|
||||
get => isDefaultNo;
|
||||
set {
|
||||
if (value != isDefaultNo)
|
||||
{
|
||||
isDefaultNo = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDefaultAbort {
|
||||
get => isDefaultAbort;
|
||||
set {
|
||||
if (value != isDefaultAbort)
|
||||
{
|
||||
isDefaultAbort = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDefaultRetry {
|
||||
get => isDefaultRetry;
|
||||
set {
|
||||
if (value != isDefaultRetry)
|
||||
{ isDefaultRetry = value; NotifyPropertyChanged(); }
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDefaultIgnore {
|
||||
get => isDefaultIgnore;
|
||||
set {
|
||||
if (value != isDefaultIgnore)
|
||||
{
|
||||
isDefaultIgnore = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endregion properties
|
||||
|
||||
#region constructors
|
||||
|
||||
private MessageBoxEx()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = this;
|
||||
LargestButtonWidth();
|
||||
}
|
||||
|
||||
public MessageBoxEx(string msg, string title, MessageBoxButton buttons = MessageBoxButton.OK,
|
||||
MessageBoxImage image = MessageBoxImage.None)
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = this;
|
||||
Init(msg, title, buttons, image);
|
||||
}
|
||||
|
||||
public MessageBoxEx(string msg, string title, MessageBoxButtonEx buttons = MessageBoxButtonEx.OK,
|
||||
MessageBoxImage image = MessageBoxImage.None)
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = this;
|
||||
Init(msg, title, buttons, image);
|
||||
}
|
||||
|
||||
#endregion constructors
|
||||
|
||||
#region non-static methods
|
||||
|
||||
protected virtual void Init(string msg, string title, MessageBoxButton buttons, MessageBoxImage image)
|
||||
{
|
||||
InitTop(msg, title);
|
||||
usingExButtons = false;
|
||||
ButtonsEx = null;
|
||||
Buttons = buttons;
|
||||
SetButtonTemplates();
|
||||
InitBottom(image);
|
||||
FindDefaultButton(staticButtonDefault);
|
||||
}
|
||||
|
||||
protected virtual void Init(string msg, string title, MessageBoxButtonEx buttons, MessageBoxImage image)
|
||||
{
|
||||
InitTop(msg, title);
|
||||
usingExButtons = true;
|
||||
Buttons = null;
|
||||
ButtonsEx = buttons;
|
||||
SetButtonTemplates();
|
||||
InitBottom(image);
|
||||
FindDefaultButtonEx(staticButtonDefault);
|
||||
}
|
||||
|
||||
void InitTop(string msg, string title)
|
||||
{
|
||||
// determine whether or not to show the details pane and checkbox
|
||||
ShowDetailsBtn = (string.IsNullOrEmpty(DetailsText)) ? Visibility.Collapsed : Visibility.Visible;
|
||||
ShowCheckBox = (CheckBoxData == null) ? Visibility.Collapsed : Visibility.Visible;
|
||||
|
||||
// Well, the binding for family/size don't appear to be working, so I have to set them
|
||||
// manually. Weird...
|
||||
FontFamily = MsgFontFamily;
|
||||
FontSize = MsgFontSize;
|
||||
LargestButtonWidth();
|
||||
|
||||
// configure the form based on specified criteria
|
||||
Message = msg;
|
||||
MessageTitle = (string.IsNullOrEmpty(title.Trim())) ? "Application Message" : title;
|
||||
|
||||
// url (if specified)
|
||||
if (Url != null)
|
||||
{
|
||||
tbUrl.Text = (string.IsNullOrEmpty(UrlDisplayName)) ? Url.ToString() : UrlDisplayName;
|
||||
tbUrl.ToolTip = new ToolTip() { Content = Url.ToString() };
|
||||
}
|
||||
}
|
||||
|
||||
private void InitBottom(MessageBoxImage image)
|
||||
{
|
||||
MessageBackground = (MessageBackground == null) ? new SolidColorBrush(Colors.White) : MessageBackground;
|
||||
MessageForeground = (MessageForeground == null) ? new SolidColorBrush(Colors.Black) : MessageForeground;
|
||||
ButtonBackground = (ButtonBackground == null) ? new SolidColorBrush(ColorFromString("#cdcdcd")) : ButtonBackground;
|
||||
|
||||
MessageIcon = null;
|
||||
|
||||
msgBoxImage = image;
|
||||
|
||||
if (DelegateObj != null)
|
||||
{
|
||||
Style style = (Style)(FindResource("ImageOpacityChanger"));
|
||||
|
||||
if (style != null)
|
||||
{
|
||||
imgMsgBoxIcon.Style = style;
|
||||
|
||||
if (!string.IsNullOrEmpty(DelegateToolTip))
|
||||
{
|
||||
System.Windows.Controls.ToolTip tooltip = new System.Windows.Controls.ToolTip() { Content = DelegateToolTip };
|
||||
// for some reason, Image elements can't do tooltips, so I assign the tootip
|
||||
// to the parent grid. This seems to work fine.
|
||||
imgGrid.ToolTip = tooltip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// multiple images have the same ordinal value, and are indicated in the comments below.
|
||||
// WTF Microsoft?
|
||||
switch ((int)image)
|
||||
{
|
||||
case 16: // MessageBoxImage.Error, MessageBoxImage.Stop, MessageBox.Image.Hand
|
||||
{
|
||||
MessageIcon = GetIcon(SystemIcons.Error);
|
||||
|
||||
if (!isSilent)
|
||||
SystemSounds.Hand.Play();
|
||||
}
|
||||
break;
|
||||
|
||||
case 64: // MessageBoxImage.Information, MessageBoxImage.Asterisk
|
||||
{
|
||||
MessageIcon = GetIcon(SystemIcons.Information);
|
||||
|
||||
if (!isSilent)
|
||||
SystemSounds.Asterisk.Play();
|
||||
}
|
||||
break;
|
||||
|
||||
case 32: // MessageBoxImage.Question
|
||||
{
|
||||
MessageIcon = GetIcon(SystemIcons.Question);
|
||||
|
||||
if (!isSilent)
|
||||
SystemSounds.Question.Play();
|
||||
}
|
||||
break;
|
||||
|
||||
case 48: // MessageBoxImage.Warning, MessageBoxImage.Exclamation
|
||||
{
|
||||
MessageIcon = GetIcon(SystemIcons.Warning);
|
||||
|
||||
if (!isSilent)
|
||||
SystemSounds.Exclamation.Play();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
MessageIcon = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public ImageSource GetIcon(Icon icon)
|
||||
{
|
||||
BitmapSource image = Imaging.CreateBitmapSourceFromHIcon(
|
||||
icon.Handle, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
|
||||
return image;
|
||||
}
|
||||
|
||||
protected virtual void CenterInScreen()
|
||||
{
|
||||
double width = ActualWidth;
|
||||
double height = ActualHeight;
|
||||
Left = (SystemParameters.WorkArea.Width - width) / 2 + SystemParameters.WorkArea.Left;
|
||||
Top = (SystemParameters.WorkArea.Height - height) / 2 + SystemParameters.WorkArea.Top;
|
||||
}
|
||||
|
||||
protected void LargestButtonWidth()
|
||||
{
|
||||
Typeface typeface = new Typeface(FontFamily, FontStyle, FontWeight, FontStretch);
|
||||
|
||||
StackPanel panel = (StackPanel)stackButtons.Child;
|
||||
double width = 0;
|
||||
string largestName = string.Empty;
|
||||
foreach (System.Windows.Controls.Button button in panel.Children)
|
||||
{
|
||||
// Using the FormattedText object
|
||||
// will strip whitespace before measuring the text, so we convert spaces to double
|
||||
// hyphens to compensate (I like to pad button Content with a leading and trailing
|
||||
// space) so that the button is wide enough to present a more padded appearance.
|
||||
FormattedText formattedText = new FormattedText(
|
||||
(button.Name == "btnDetails") ? "--Details--" : ((string)(button.Content)).Replace(" ", "--"),
|
||||
CultureInfo.CurrentUICulture,
|
||||
System.Windows.FlowDirection.LeftToRight,
|
||||
typeface,
|
||||
FontSize = FontSize,
|
||||
System.Windows.Media.Brushes.Black,
|
||||
VisualTreeHelper.GetDpi(this).PixelsPerDip);
|
||||
|
||||
if (width < formattedText.Width)
|
||||
largestName = button.Name;
|
||||
|
||||
width = Math.Max(width, formattedText.Width);
|
||||
}
|
||||
ButtonWidth = Math.Ceiling(width/*width + polyArrow.Width+polyArrow.Margin.Right+Margin.Left*/);
|
||||
}
|
||||
|
||||
void SetButtonTemplates()
|
||||
{
|
||||
// set the button template (if specified)
|
||||
if (!string.IsNullOrEmpty(ButtonTemplateName))
|
||||
{
|
||||
bool foundResource = true;
|
||||
|
||||
try
|
||||
{
|
||||
FindResource(ButtonTemplateName);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
foundResource = false;
|
||||
}
|
||||
|
||||
if (foundResource)
|
||||
{
|
||||
btnOK.SetResourceReference(Control.TemplateProperty, ButtonTemplateName);
|
||||
btnYes.SetResourceReference(Control.TemplateProperty, ButtonTemplateName);
|
||||
btnNo.SetResourceReference(Control.TemplateProperty, ButtonTemplateName);
|
||||
btnCancel.SetResourceReference(Control.TemplateProperty, ButtonTemplateName);
|
||||
btnAbort.SetResourceReference(Control.TemplateProperty, ButtonTemplateName);
|
||||
btnRetry.SetResourceReference(Control.TemplateProperty, ButtonTemplateName);
|
||||
btnIgnore.SetResourceReference(Control.TemplateProperty, ButtonTemplateName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void FindDefaultButtonEx(MessageBoxButtonDefault buttonDefault)
|
||||
{
|
||||
// determine default button
|
||||
IsDefaultOK = false;
|
||||
IsDefaultCancel = false;
|
||||
IsDefaultYes = false;
|
||||
IsDefaultNo = false;
|
||||
IsDefaultAbort = false;
|
||||
IsDefaultRetry = false;
|
||||
IsDefaultIgnore = false;
|
||||
|
||||
if (buttonDefault != MessageBoxButtonDefault.None)
|
||||
{
|
||||
switch (ButtonsEx)
|
||||
{
|
||||
case MessageBoxButtonEx.OK:
|
||||
IsDefaultOK = true;
|
||||
break;
|
||||
case MessageBoxButtonEx.OKCancel:
|
||||
{
|
||||
switch (buttonDefault)
|
||||
{
|
||||
case MessageBoxButtonDefault.Button1:
|
||||
case MessageBoxButtonDefault.OK:
|
||||
case MessageBoxButtonDefault.MostPositive:
|
||||
IsDefaultOK = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Button2:
|
||||
case MessageBoxButtonDefault.Cancel:
|
||||
case MessageBoxButtonDefault.LeastPositive:
|
||||
IsDefaultCancel = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Forms:
|
||||
default:
|
||||
IsDefaultOK = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MessageBoxButtonEx.YesNoCancel:
|
||||
{
|
||||
switch (buttonDefault)
|
||||
{
|
||||
case MessageBoxButtonDefault.Button1:
|
||||
case MessageBoxButtonDefault.Yes:
|
||||
break;
|
||||
case MessageBoxButtonDefault.MostPositive:
|
||||
IsDefaultYes = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Button2:
|
||||
case MessageBoxButtonDefault.No:
|
||||
IsDefaultNo = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Button3:
|
||||
case MessageBoxButtonDefault.Cancel:
|
||||
case MessageBoxButtonDefault.LeastPositive:
|
||||
IsDefaultCancel = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Forms:
|
||||
default:
|
||||
IsDefaultYes = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MessageBoxButtonEx.YesNo:
|
||||
{
|
||||
switch (buttonDefault)
|
||||
{
|
||||
case MessageBoxButtonDefault.Button1:
|
||||
case MessageBoxButtonDefault.Yes:
|
||||
case MessageBoxButtonDefault.MostPositive:
|
||||
IsDefaultYes = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Button2:
|
||||
case MessageBoxButtonDefault.No:
|
||||
case MessageBoxButtonDefault.LeastPositive:
|
||||
IsDefaultNo = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Forms:
|
||||
default:
|
||||
IsDefaultYes = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MessageBoxButtonEx.RetryCancel:
|
||||
{
|
||||
switch (buttonDefault)
|
||||
{
|
||||
case MessageBoxButtonDefault.Button1:
|
||||
case MessageBoxButtonDefault.Retry:
|
||||
case MessageBoxButtonDefault.MostPositive:
|
||||
IsDefaultRetry = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Button2:
|
||||
case MessageBoxButtonDefault.Cancel:
|
||||
case MessageBoxButtonDefault.LeastPositive:
|
||||
IsDefaultCancel = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Forms:
|
||||
default:
|
||||
IsDefaultRetry = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MessageBoxButtonEx.AbortRetryIgnore:
|
||||
{
|
||||
switch (buttonDefault)
|
||||
{
|
||||
case MessageBoxButtonDefault.Button1:
|
||||
case MessageBoxButtonDefault.Abort:
|
||||
case MessageBoxButtonDefault.LeastPositive:
|
||||
IsDefaultAbort = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Button2:
|
||||
case MessageBoxButtonDefault.Retry:
|
||||
IsDefaultRetry = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Button3:
|
||||
case MessageBoxButtonDefault.Ignore:
|
||||
case MessageBoxButtonDefault.MostPositive:
|
||||
IsDefaultIgnore = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Forms:
|
||||
default:
|
||||
IsDefaultAbort = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void FindDefaultButton(MessageBoxButtonDefault buttonDefault)
|
||||
{
|
||||
// determine default button
|
||||
IsDefaultOK = false;
|
||||
IsDefaultCancel = false;
|
||||
IsDefaultYes = false;
|
||||
IsDefaultNo = false;
|
||||
IsDefaultAbort = false;
|
||||
IsDefaultRetry = false;
|
||||
IsDefaultIgnore = false;
|
||||
|
||||
if (buttonDefault != MessageBoxButtonDefault.None)
|
||||
{
|
||||
switch (Buttons)
|
||||
{
|
||||
case MessageBoxButton.OK:
|
||||
IsDefaultOK = true;
|
||||
break;
|
||||
case MessageBoxButton.OKCancel:
|
||||
{
|
||||
switch (buttonDefault)
|
||||
{
|
||||
case MessageBoxButtonDefault.Button1:
|
||||
case MessageBoxButtonDefault.OK:
|
||||
case MessageBoxButtonDefault.MostPositive:
|
||||
IsDefaultOK = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Button2:
|
||||
case MessageBoxButtonDefault.Cancel:
|
||||
case MessageBoxButtonDefault.LeastPositive:
|
||||
IsDefaultCancel = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Forms:
|
||||
default:
|
||||
IsDefaultOK = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MessageBoxButton.YesNoCancel:
|
||||
{
|
||||
switch (buttonDefault)
|
||||
{
|
||||
case MessageBoxButtonDefault.Button1:
|
||||
case MessageBoxButtonDefault.Yes:
|
||||
break;
|
||||
case MessageBoxButtonDefault.MostPositive:
|
||||
IsDefaultYes = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Button2:
|
||||
case MessageBoxButtonDefault.No:
|
||||
IsDefaultNo = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Button3:
|
||||
case MessageBoxButtonDefault.Cancel:
|
||||
case MessageBoxButtonDefault.LeastPositive:
|
||||
IsDefaultCancel = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Forms:
|
||||
default:
|
||||
IsDefaultYes = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MessageBoxButton.YesNo:
|
||||
{
|
||||
switch (buttonDefault)
|
||||
{
|
||||
case MessageBoxButtonDefault.Button1:
|
||||
case MessageBoxButtonDefault.Yes:
|
||||
case MessageBoxButtonDefault.MostPositive:
|
||||
IsDefaultYes = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Button2:
|
||||
case MessageBoxButtonDefault.No:
|
||||
case MessageBoxButtonDefault.LeastPositive:
|
||||
IsDefaultNo = true;
|
||||
break;
|
||||
case MessageBoxButtonDefault.Forms:
|
||||
default:
|
||||
IsDefaultYes = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion non-static methods
|
||||
|
||||
#region event handlers
|
||||
|
||||
#region buttons
|
||||
|
||||
/// <summary>
|
||||
/// Handle the click event for the OK button
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void BtnOK_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
this.MessageResult = MessageBoxResult.OK;
|
||||
this.MessageResultEx = MessageBoxResultEx.OK;
|
||||
this.DialogResult = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle the click event for the Yes button
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void BtnYes_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
this.MessageResult = MessageBoxResult.Yes;
|
||||
this.MessageResultEx = MessageBoxResultEx.Yes;
|
||||
this.DialogResult = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle the click event for the No button
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void BtnNo_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
this.MessageResult = MessageBoxResult.No;
|
||||
this.MessageResultEx = MessageBoxResultEx.No;
|
||||
this.DialogResult = true;
|
||||
}
|
||||
|
||||
private void BtnAbort_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
this.MessageResult = MessageBoxResult.None;
|
||||
this.MessageResultEx = MessageBoxResultEx.Abort;
|
||||
this.DialogResult = true;
|
||||
}
|
||||
|
||||
private void BtnRetry_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
this.MessageResult = MessageBoxResult.None;
|
||||
this.MessageResultEx = MessageBoxResultEx.Retry;
|
||||
this.DialogResult = true;
|
||||
}
|
||||
|
||||
private void BtnIgnore_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
this.MessageResult = MessageBoxResult.None;
|
||||
this.MessageResultEx = MessageBoxResultEx.Ignore;
|
||||
this.DialogResult = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle the click event for the Cancel button
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void BtnCancel_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
this.MessageResult = MessageBoxResult.Cancel;
|
||||
this.MessageResultEx = MessageBoxResultEx.Cancel;
|
||||
this.DialogResult = true;
|
||||
}
|
||||
|
||||
#endregion buttons
|
||||
|
||||
void NotifiableWindow_SizeChanged(object sender, SizeChangedEventArgs e) => CenterInScreen();
|
||||
|
||||
void Window_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
imgMsgBoxIcon.ToolTip = (msgBoxImage == MessageBoxImage.Error) ? MsgBoxIconToolTip : null;
|
||||
}
|
||||
|
||||
void Window_Closing(object sender, CancelEventArgs e)
|
||||
{
|
||||
DetailsText = null;
|
||||
CheckBoxData = null;
|
||||
staticButtonDefault = MessageBoxButtonDefault.Forms;
|
||||
|
||||
if (MessageResult == MessageBoxResult.None)
|
||||
{
|
||||
if (usingExButtons)
|
||||
{
|
||||
switch (ButtonsEx)
|
||||
{
|
||||
case MessageBoxButtonEx.OK:
|
||||
MessageResultEx = MessageBoxResultEx.OK;
|
||||
break;
|
||||
case MessageBoxButtonEx.YesNoCancel:
|
||||
case MessageBoxButtonEx.OKCancel:
|
||||
case MessageBoxButtonEx.RetryCancel:
|
||||
case MessageBoxButtonEx.AbortRetryIgnore:
|
||||
MessageResultEx = MessageBoxResultEx.Cancel;
|
||||
break;
|
||||
case MessageBoxButtonEx.YesNo:
|
||||
MessageResultEx = MessageBoxResultEx.No;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (Buttons)
|
||||
{
|
||||
case MessageBoxButton.OK:
|
||||
MessageResult = MessageBoxResult.OK;
|
||||
break;
|
||||
case MessageBoxButton.YesNoCancel:
|
||||
case MessageBoxButton.OKCancel:
|
||||
MessageResult = MessageBoxResult.Cancel;
|
||||
break;
|
||||
case MessageBoxButton.YesNo:
|
||||
MessageResult = MessageBoxResult.No;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ImgMsgBoxIcon_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (DelegateObj != null && msgBoxImage == MessageBoxImage.Error && Buttons == MessageBoxButton.OK)
|
||||
{
|
||||
DelegateObj.PerformAction(Message);
|
||||
|
||||
if (ExitAfterErrorAction)
|
||||
{
|
||||
MessageResult = MessageBoxResult.None;
|
||||
DialogResult = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TbUrl_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) => ProcessHelp.ShellExecute(Url?.ToString());
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool EnableMenuItem(IntPtr hMenu, uint uIDEnableItem, uint uEnable);
|
||||
|
||||
private const uint MF_BYCOMMAND = 0x00000000;
|
||||
private const uint MF_GRAYED = 0x00000001;
|
||||
private const uint SC_CLOSE = 0xF060;
|
||||
|
||||
void Window_SourceInitialized(object sender, EventArgs e)
|
||||
{
|
||||
if (!enableCloseButton)
|
||||
{
|
||||
var hWnd = new WindowInteropHelper(this);
|
||||
var sysMenu = GetSystemMenu(hWnd.Handle, false);
|
||||
EnableMenuItem(sysMenu, SC_CLOSE, MF_BYCOMMAND | MF_GRAYED);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion event handlers
|
||||
}
|
||||
56
src/MpvNet.Windows/WPF/MsgBox/MsgBoxExCheckBoxData.cs
Normal file
56
src/MpvNet.Windows/WPF/MsgBox/MsgBoxExCheckBoxData.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace MpvNet.Windows.WPF.MsgBox;
|
||||
|
||||
public class MsgBoxExCheckBoxData : INotifyPropertyChanged
|
||||
{
|
||||
private bool isModified = false;
|
||||
|
||||
public bool IsModified {
|
||||
get => isModified;
|
||||
set {
|
||||
if (value != isModified)
|
||||
{
|
||||
isModified = true;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
|
||||
{
|
||||
if (PropertyChanged != null)
|
||||
{
|
||||
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
|
||||
|
||||
if (propertyName != "IsModified")
|
||||
IsModified = true;
|
||||
}
|
||||
}
|
||||
|
||||
private string? checkBoxText;
|
||||
private bool checkBoxIsChecked;
|
||||
|
||||
public string? CheckBoxText {
|
||||
get => checkBoxText;
|
||||
set {
|
||||
if (value != checkBoxText)
|
||||
checkBoxText = value; NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public bool CheckBoxIsChecked {
|
||||
get => checkBoxIsChecked;
|
||||
set {
|
||||
if (value != checkBoxIsChecked)
|
||||
{
|
||||
checkBoxIsChecked = value;
|
||||
NotifyPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
16
src/MpvNet.Windows/WPF/MsgBox/MsgBoxExDelegate.cs
Normal file
16
src/MpvNet.Windows/WPF/MsgBox/MsgBoxExDelegate.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
using System.Windows;
|
||||
|
||||
namespace MpvNet.Windows.WPF.MsgBox;
|
||||
|
||||
public abstract class MsgBoxExDelegate
|
||||
{
|
||||
public string? Message { get; set; }
|
||||
public string? Details { get; set; }
|
||||
public DateTime MessageDate { get; set; }
|
||||
|
||||
public virtual MessageBoxResult PerformAction(string message, string? details = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
215
src/MpvNet.Windows/WPF/MsgBox/MsgBoxExStatic.cs
Normal file
215
src/MpvNet.Windows/WPF/MsgBox/MsgBoxExStatic.cs
Normal file
@@ -0,0 +1,215 @@
|
||||
|
||||
// https://www.codeproject.com/Articles/5290638/Customizable-WPF-MessageBox
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Drawing.Text;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace MpvNet.Windows.WPF.MsgBox;
|
||||
|
||||
public partial class MessageBoxEx : Window, INotifyPropertyChanged
|
||||
{
|
||||
#region fields
|
||||
|
||||
private static double screenWidth = SystemParameters.WorkArea.Width - 100;
|
||||
|
||||
private static bool enableCloseButton = true;
|
||||
private static bool isSilent = false;
|
||||
private static List<string> installedFonts = new List<string>();
|
||||
public static MessageBoxButtonDefault staticButtonDefault;
|
||||
|
||||
#endregion fields
|
||||
|
||||
#region properties
|
||||
|
||||
public static Color DefaultUrlForegroundColor => Colors.Blue;
|
||||
|
||||
private static string? MsgBoxIconToolTip { get; set; }
|
||||
|
||||
protected static MsgBoxExDelegate? DelegateObj { get; set; }
|
||||
protected static bool ExitAfterErrorAction { get; set; }
|
||||
|
||||
public static ContentControl? ParentWindow { get; set; }
|
||||
|
||||
public static string? ButtonTemplateName { get; set; }
|
||||
|
||||
public static Brush? MessageBackground { get; set; }
|
||||
|
||||
public static Brush? MessageForeground { get; set; }
|
||||
|
||||
public static Brush? ButtonBackground { get; set; }
|
||||
|
||||
public static double MaxFormWidth { get; set; } = screenWidth;
|
||||
|
||||
public static Visibility ShowDetailsBtn { get; set; } = Visibility.Collapsed;
|
||||
|
||||
public static string? DetailsText { get; set; }
|
||||
|
||||
public static Visibility ShowCheckBox { get; set; } = Visibility.Collapsed;
|
||||
|
||||
public static MsgBoxExCheckBoxData? CheckBoxData { get; set; } = null;
|
||||
|
||||
public static FontFamily MsgFontFamily { get; set; } = new FontFamily("Segoe UI");
|
||||
|
||||
public static double MsgFontSize { get; set; } = 12;
|
||||
|
||||
public static Uri? Url { get; set; } = null;
|
||||
|
||||
public static Visibility ShowUrl { get; set; } = Visibility.Collapsed;
|
||||
|
||||
public static string? UrlDisplayName { get; set; } = null;
|
||||
|
||||
public static SolidColorBrush UrlForeground { get; set; } = new SolidColorBrush(DefaultUrlForegroundColor);
|
||||
|
||||
public static string? DelegateToolTip { get; set; }
|
||||
|
||||
#endregion properties
|
||||
|
||||
#region methods
|
||||
|
||||
public static void SetFont(string familyName) => MsgFontFamily = new FontFamily(familyName);
|
||||
|
||||
public static MessageBoxResult OpenMessageBox(
|
||||
string msg, string title, MessageBoxButton buttons, MessageBoxImage image)
|
||||
{
|
||||
MessageBoxEx window = new MessageBoxEx(msg, title, buttons, image);
|
||||
SetOwner(window);
|
||||
window.ShowDialog();
|
||||
return window.MessageResult;
|
||||
}
|
||||
|
||||
public static MessageBoxResultEx OpenMessageBox(
|
||||
string msg,
|
||||
string title,
|
||||
MessageBoxButtonEx buttons,
|
||||
MessageBoxImage image)
|
||||
{
|
||||
MessageBoxEx window = new MessageBoxEx(msg, title, buttons, image);
|
||||
SetOwner(window);
|
||||
window.ShowDialog();
|
||||
return window.MessageResultEx;
|
||||
}
|
||||
|
||||
public static void SetOwner(Window window)
|
||||
{
|
||||
IntPtr ownerHandle = GetOwnerHandle();
|
||||
|
||||
if (ownerHandle != IntPtr.Zero)
|
||||
new WindowInteropHelper(window).Owner = ownerHandle;
|
||||
}
|
||||
|
||||
public static IntPtr GetOwnerHandle()
|
||||
{
|
||||
IntPtr foregroundWindow = GetForegroundWindow();
|
||||
GetWindowThreadProcessId(foregroundWindow, out var procID);
|
||||
|
||||
using (var proc = Process.GetCurrentProcess())
|
||||
if (proc.Id == procID)
|
||||
return foregroundWindow;
|
||||
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern IntPtr GetForegroundWindow();
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
|
||||
|
||||
public static Color ColorFromString(string colorString)
|
||||
{
|
||||
Color wpfColor = Colors.Black;
|
||||
|
||||
try {
|
||||
wpfColor = (Color)ColorConverter.ConvertFromString(colorString);
|
||||
} catch (Exception) { }
|
||||
|
||||
return wpfColor;
|
||||
}
|
||||
|
||||
public static void SetFont()
|
||||
{
|
||||
MsgFontFamily = Application.Current.MainWindow.FontFamily;
|
||||
MsgFontSize = Application.Current.MainWindow.FontSize;
|
||||
}
|
||||
|
||||
public static void SetFont(ContentControl parent)
|
||||
{
|
||||
MsgFontFamily = parent.FontFamily;
|
||||
MsgFontSize = parent.FontSize;
|
||||
}
|
||||
|
||||
public static void SetFont(string familyName, double size)
|
||||
{
|
||||
if (!IsFontFamilyValid(familyName))
|
||||
if (!string.IsNullOrEmpty(familyName))
|
||||
MsgFontFamily = new FontFamily(familyName);
|
||||
MsgFontSize = Math.Max(1.0, size);
|
||||
}
|
||||
|
||||
private static bool IsFontFamilyValid(string name)
|
||||
{
|
||||
if (installedFonts.Count == 0)
|
||||
using (InstalledFontCollection fontsCollection = new InstalledFontCollection())
|
||||
installedFonts = (from x in fontsCollection.Families select x.Name).ToList();
|
||||
return installedFonts.Contains(name);
|
||||
}
|
||||
|
||||
public static void SetButtonTemplateName(string name)
|
||||
{
|
||||
ButtonTemplateName = name;
|
||||
}
|
||||
|
||||
public static void SetMaxFormWidth(double value)
|
||||
{
|
||||
MaxFormWidth = Math.Max(value, 300);
|
||||
double minWidth = 300;
|
||||
MaxFormWidth = Math.Max(minWidth, Math.Min(value, screenWidth));
|
||||
}
|
||||
|
||||
public static void ResetToDefaults()
|
||||
{
|
||||
MsgFontSize = 12d;
|
||||
MsgFontFamily = new FontFamily("Segoe UI");
|
||||
DelegateObj = null;
|
||||
DetailsText = null;
|
||||
MessageForeground = null;
|
||||
MessageBackground = null;
|
||||
ButtonBackground = null;
|
||||
ParentWindow = null;
|
||||
isSilent = false;
|
||||
enableCloseButton = true;
|
||||
ButtonTemplateName = null;
|
||||
MsgBoxIconToolTip = null;
|
||||
ShowCheckBox = Visibility.Collapsed;
|
||||
CheckBoxData = null;
|
||||
ExitAfterErrorAction = false;
|
||||
MaxFormWidth = 800;
|
||||
Url = null;
|
||||
ShowUrl = Visibility.Collapsed;
|
||||
UrlDisplayName = null;
|
||||
UrlForeground = new SolidColorBrush(DefaultUrlForegroundColor);
|
||||
staticButtonDefault = MessageBoxButtonDefault.Forms;
|
||||
}
|
||||
|
||||
public static void EnableCloseButton(bool enable)
|
||||
{
|
||||
enableCloseButton = enable;
|
||||
}
|
||||
|
||||
public static void SetAsSilent(bool quiet)
|
||||
{
|
||||
isSilent = quiet;
|
||||
}
|
||||
|
||||
public static void SetDefaultButton(MessageBoxButtonDefault buttonDefault)
|
||||
{
|
||||
staticButtonDefault = buttonDefault;
|
||||
}
|
||||
|
||||
#endregion methods
|
||||
}
|
||||
24
src/MpvNet.Windows/WPF/MsgBox/MsgBoxExtendedFunctionality.cs
Normal file
24
src/MpvNet.Windows/WPF/MsgBox/MsgBoxExtendedFunctionality.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
|
||||
namespace MpvNet.Windows.WPF.MsgBox;
|
||||
|
||||
public class MsgBoxExtendedFunctionality
|
||||
{
|
||||
public MessageBoxButtonDefault ButtonDefault { get; set; }
|
||||
public string? DetailsText { get; set; }
|
||||
public MsgBoxExCheckBoxData? CheckBoxData { get; set; }
|
||||
public MsgBoxExDelegate? MessageDelegate { get; set; }
|
||||
public bool ExitAfterAction { get; set; }
|
||||
public string DelegateToolTip { get; set; }
|
||||
|
||||
public MsgBoxUrl? URL { get; set; }
|
||||
|
||||
public MsgBoxExtendedFunctionality()
|
||||
{
|
||||
ButtonDefault = MessageBoxButtonDefault.Forms;
|
||||
DetailsText = null;
|
||||
CheckBoxData = null;
|
||||
MessageDelegate = null;
|
||||
URL = null;
|
||||
DelegateToolTip = "Click this icon for additional info/actions.";
|
||||
}
|
||||
}
|
||||
13
src/MpvNet.Windows/WPF/MsgBox/MsgBoxUrl.cs
Normal file
13
src/MpvNet.Windows/WPF/MsgBox/MsgBoxUrl.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace MpvNet.Windows.WPF.MsgBox;
|
||||
|
||||
public class MsgBoxUrl
|
||||
{
|
||||
public Uri? URL { get; set; }
|
||||
public string? DisplayName { get; set; }
|
||||
public Color Foreground { get; set; }
|
||||
|
||||
public MsgBoxUrl() => Foreground = MessageBoxEx.DefaultUrlForegroundColor;
|
||||
}
|
||||
15
src/MpvNet.Windows/WPF/MsgBox/MsgEnum.cs
Normal file
15
src/MpvNet.Windows/WPF/MsgBox/MsgEnum.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
namespace MpvNet.Windows.WPF.MsgBox;
|
||||
|
||||
public enum MessageBoxButtonEx { OK = 0, OKCancel, AbortRetryIgnore, YesNoCancel, YesNo, RetryCancel }
|
||||
|
||||
public enum MessageBoxResultEx { None = 0, OK, Cancel, Abort, Retry, Ignore, Yes, No }
|
||||
|
||||
public enum MessageBoxButtonDefault
|
||||
{
|
||||
OK, Cancel, Yes, No, Abort, Retry, Ignore, // specific button
|
||||
Button1, Button2, Button3, // button by ordinal left-to-right position
|
||||
MostPositive, LeastPositive, // button by positivity
|
||||
Forms, // button according to the Windows.Forms standard messagebox
|
||||
None // no default button
|
||||
}
|
||||
1034
src/MpvNet.Windows/WPF/Resources.xaml
Normal file
1034
src/MpvNet.Windows/WPF/Resources.xaml
Normal file
File diff suppressed because it is too large
Load Diff
14
src/MpvNet.Windows/WPF/ViewModels/AboutViewModel.cs
Normal file
14
src/MpvNet.Windows/WPF/ViewModels/AboutViewModel.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
namespace MpvNet.Windows.WPF.ViewModels;
|
||||
|
||||
public partial class AboutViewModel : ViewModelBase
|
||||
{
|
||||
public Action? CloseAction { get; set; }
|
||||
|
||||
public string About { get; } = AppClass.About;
|
||||
|
||||
[RelayCommand]
|
||||
public void Close() => CloseAction!();
|
||||
}
|
||||
76
src/MpvNet.Windows/WPF/ViewModels/NodeViewModel.cs
Normal file
76
src/MpvNet.Windows/WPF/ViewModels/NodeViewModel.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
using MpvNet.Windows.UI;
|
||||
|
||||
namespace MpvNet.Windows.WPF.ViewModels;
|
||||
|
||||
public class NodeViewModel : ObservableObject
|
||||
{
|
||||
readonly List<NodeViewModel> _children;
|
||||
readonly NodeViewModel? _parent;
|
||||
readonly TreeNode _node;
|
||||
|
||||
bool _isExpanded;
|
||||
bool _isSelected;
|
||||
|
||||
public NodeViewModel(TreeNode node) : this(node, null)
|
||||
{
|
||||
}
|
||||
|
||||
public NodeViewModel(TreeNode node, NodeViewModel? parent)
|
||||
{
|
||||
_node = node;
|
||||
_parent = parent;
|
||||
|
||||
_children = new List<NodeViewModel>(
|
||||
_node.Children.Select(i => new NodeViewModel(i, this)).ToList());
|
||||
}
|
||||
|
||||
public List<NodeViewModel> Children => _children;
|
||||
|
||||
public string Name => _node.Name;
|
||||
|
||||
public string Path {
|
||||
get {
|
||||
string path = Name;
|
||||
NodeViewModel? parent = Parent;
|
||||
|
||||
while (!string.IsNullOrEmpty(parent?.Name))
|
||||
{
|
||||
path = parent.Name + "/" + path;
|
||||
parent = parent.Parent;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
public NodeViewModel? Parent => _parent;
|
||||
|
||||
public bool IsExpanded
|
||||
{
|
||||
get => _isExpanded;
|
||||
set
|
||||
{
|
||||
SetProperty(ref _isExpanded, value);
|
||||
|
||||
if (_isExpanded && _parent != null)
|
||||
_parent.IsExpanded = true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSelected
|
||||
{
|
||||
get => _isSelected;
|
||||
set => SetProperty(ref _isSelected, value);
|
||||
}
|
||||
|
||||
public bool NameContains(string text)
|
||||
{
|
||||
if (text == "")
|
||||
return false;
|
||||
|
||||
return Name.IndexOf(text, StringComparison.InvariantCultureIgnoreCase) > -1;
|
||||
}
|
||||
}
|
||||
11
src/MpvNet.Windows/WPF/ViewModels/ViewModelBase.cs
Normal file
11
src/MpvNet.Windows/WPF/ViewModels/ViewModelBase.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
using MpvNet.Windows.UI;
|
||||
|
||||
namespace MpvNet.Windows.WPF.ViewModels;
|
||||
|
||||
public class ViewModelBase : ObservableObject
|
||||
{
|
||||
public Theme Theme => Theme.Current!;
|
||||
}
|
||||
46
src/MpvNet.Windows/WPF/Views/AboutWindow.xaml
Normal file
46
src/MpvNet.Windows/WPF/Views/AboutWindow.xaml
Normal file
@@ -0,0 +1,46 @@
|
||||
<Window
|
||||
x:Class="MpvNet.Windows.WPF.Views.AboutWindow"
|
||||
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"
|
||||
mc:Ignorable="d"
|
||||
|
||||
Title="About mpv.net"
|
||||
FontSize="16"
|
||||
ShowInTaskbar="False"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
ResizeMode="NoResize"
|
||||
SizeToContent="WidthAndHeight"
|
||||
Foreground="{Binding Theme.Foreground}"
|
||||
Background="{Binding Theme.Background}"
|
||||
>
|
||||
|
||||
<Window.InputBindings>
|
||||
<KeyBinding Key="Esc" Command="{Binding CloseCommand}"/>
|
||||
</Window.InputBindings>
|
||||
|
||||
<Grid>
|
||||
<StackPanel HorizontalAlignment="Center">
|
||||
<TextBox
|
||||
IsReadOnly="True"
|
||||
FontSize="30"
|
||||
TextAlignment="Center"
|
||||
Margin="10,10,10,0"
|
||||
BorderThickness="0"
|
||||
Foreground="{Binding Theme.Foreground}"
|
||||
Background="{Binding Theme.Background}"
|
||||
>mpv.net
|
||||
</TextBox>
|
||||
<TextBox
|
||||
IsReadOnly="True"
|
||||
Text="{Binding About, Mode=OneWay}"
|
||||
TextAlignment="Center"
|
||||
Margin="20,10,20,20"
|
||||
BorderThickness="0"
|
||||
Foreground="{Binding Theme.Foreground}"
|
||||
Background="{Binding Theme.Background}">
|
||||
</TextBox>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
||||
16
src/MpvNet.Windows/WPF/Views/AboutWindow.xaml.cs
Normal file
16
src/MpvNet.Windows/WPF/Views/AboutWindow.xaml.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
using MpvNet.Windows.WPF.ViewModels;
|
||||
|
||||
namespace MpvNet.Windows.WPF.Views;
|
||||
|
||||
public partial class AboutWindow
|
||||
{
|
||||
|
||||
public AboutWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
var vm = new AboutViewModel();
|
||||
DataContext = vm;
|
||||
vm.CloseAction = Close;
|
||||
}
|
||||
}
|
||||
20
src/MpvNet.Windows/WPF/WpfApplication.cs
Normal file
20
src/MpvNet.Windows/WPF/WpfApplication.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
|
||||
using System.Windows;
|
||||
|
||||
namespace MpvNet.Windows.WPF;
|
||||
|
||||
public class WpfApplication
|
||||
{
|
||||
public static void Init()
|
||||
{
|
||||
new Application();
|
||||
|
||||
Application.Current!.ShutdownMode = ShutdownMode.OnExplicitShutdown;
|
||||
|
||||
Application.Current!.DispatcherUnhandledException += (sender, e) => Terminal.WriteError(e.Exception);
|
||||
|
||||
Application.Current?.Resources.MergedDictionaries.Add(
|
||||
Application.LoadComponent(new Uri("mpvnet;component/WPF/Resources.xaml",
|
||||
UriKind.Relative)) as ResourceDictionary);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user