mirror of
https://github.com/sourcegit-scm/sourcegit.git
synced 2024-12-23 20:47:25 -08:00
feature<FolderDialog>: remove View.FolderBrowser and use windows shell32 API to select folders
This commit is contained in:
parent
0419cf96fc
commit
6061f5a074
8 changed files with 169 additions and 213 deletions
151
src/Views/Controls/FolderDialog.cs
Normal file
151
src/Views/Controls/FolderDialog.cs
Normal file
|
@ -0,0 +1,151 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
|
||||
namespace SourceGit.Views.Controls {
|
||||
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
internal delegate Int32 BrowseCallbackProc(IntPtr hwnd, Int32 msg, IntPtr lParam, IntPtr lpData);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
internal class BrowseInfo {
|
||||
public IntPtr hwndOwner;
|
||||
public IntPtr pidlRoot;
|
||||
public IntPtr pszDisplayName;
|
||||
public String lpszTitle;
|
||||
public Int32 ulFlags;
|
||||
public BrowseCallbackProc lpfn;
|
||||
public IntPtr lParam;
|
||||
public Int32 iImage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Win32 API封装(user32.dll)
|
||||
/// </summary>
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
internal static class User32 {
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
||||
public static extern IntPtr SendMessage(HandleRef hWnd, Int32 msg, Int32 wParam, String lParam);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
||||
public static extern IntPtr SendMessage(HandleRef hWnd, Int32 msg, Int32 wParam, Int32 lParam);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Win32 API封装(ole32.dll)
|
||||
/// </summary>
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
internal static class Ole32 {
|
||||
[DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true, SetLastError = true)]
|
||||
internal static extern void CoTaskMemFree(IntPtr pv);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Win32 API封装(shell32.dll)
|
||||
/// </summary>
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
internal static class Shell32 {
|
||||
[DllImport("shell32.dll")]
|
||||
public static extern Int32 SHGetSpecialFolderLocation(IntPtr hwnd, Int32 csidl, ref IntPtr ppidl);
|
||||
|
||||
[DllImport("shell32.dll", CharSet = CharSet.Auto)]
|
||||
public static extern Boolean SHGetPathFromIDList(IntPtr pidl, IntPtr pszPath);
|
||||
|
||||
[DllImport("shell32.dll", CharSet = CharSet.Auto)]
|
||||
public static extern IntPtr SHBrowseForFolder([In] BrowseInfo lpbi);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 调用WindowsAPI打开对话目录对话框
|
||||
/// </summary>
|
||||
public class FolderDialog : Microsoft.Win32.CommonDialog {
|
||||
/// <summary>
|
||||
/// 描述信息
|
||||
/// </summary>
|
||||
public string Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 选中的目录
|
||||
/// </summary>
|
||||
public string SelectedPath { get; private set; }
|
||||
|
||||
public FolderDialog(string descKey) {
|
||||
Description = App.Text(descKey);
|
||||
SelectedPath = string.Empty;
|
||||
}
|
||||
|
||||
public override void Reset() {
|
||||
Description = string.Empty;
|
||||
SelectedPath = string.Empty;
|
||||
}
|
||||
|
||||
protected override bool RunDialog(IntPtr hwndOwner) {
|
||||
IntPtr ppidl = IntPtr.Zero;
|
||||
Shell32.SHGetSpecialFolderLocation(hwndOwner, (Int32)Environment.SpecialFolder.Desktop, ref ppidl);
|
||||
if (ppidl == IntPtr.Zero) {
|
||||
Shell32.SHGetSpecialFolderLocation(hwndOwner, 0, ref ppidl);
|
||||
if (ppidl == IntPtr.Zero) {
|
||||
Models.Exception.Raise("Failed to open folder dialog!!!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
BrowseCallbackProc callback = new BrowseCallbackProc(BrowseCallbackHandler);
|
||||
IntPtr displayName = Marshal.AllocHGlobal(260 * Marshal.SystemDefaultCharSize);
|
||||
bool ok = false;
|
||||
try {
|
||||
var info = new BrowseInfo();
|
||||
info.pidlRoot = ppidl;
|
||||
info.hwndOwner = hwndOwner;
|
||||
info.pszDisplayName = displayName;
|
||||
info.lpszTitle = Description;
|
||||
info.ulFlags = 0x0040;
|
||||
info.lpfn = callback;
|
||||
info.lParam = IntPtr.Zero;
|
||||
info.iImage = 0;
|
||||
|
||||
IntPtr result = Shell32.SHBrowseForFolder(info);
|
||||
if (result != IntPtr.Zero) {
|
||||
IntPtr pathPtr = Marshal.AllocHGlobal(260 * Marshal.SystemDefaultCharSize);
|
||||
Shell32.SHGetPathFromIDList(result, pathPtr);
|
||||
|
||||
if (pathPtr != IntPtr.Zero) {
|
||||
SelectedPath = Marshal.PtrToStringAuto(pathPtr);
|
||||
ok = true;
|
||||
Marshal.FreeHGlobal(pathPtr);
|
||||
}
|
||||
|
||||
Ole32.CoTaskMemFree(result);
|
||||
}
|
||||
} finally {
|
||||
Ole32.CoTaskMemFree(ppidl);
|
||||
if (displayName != IntPtr.Zero) Marshal.FreeHGlobal(displayName);
|
||||
callback = null;
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
private Int32 BrowseCallbackHandler(IntPtr hwnd, Int32 msg, IntPtr lParam, IntPtr lpData) {
|
||||
switch (msg) {
|
||||
case 1:
|
||||
if (!string.IsNullOrEmpty(SelectedPath)) {
|
||||
Int32 flag = Marshal.SystemDefaultCharSize == 1 ? 1126 : 1127;
|
||||
User32.SendMessage(new HandleRef(null, hwnd), flag, 1, SelectedPath);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (lParam != IntPtr.Zero) {
|
||||
IntPtr pathPtr = Marshal.AllocHGlobal(260 * Marshal.SystemDefaultCharSize);
|
||||
bool flag = Shell32.SHGetPathFromIDList(lParam, pathPtr);
|
||||
Marshal.FreeHGlobal(pathPtr);
|
||||
User32.SendMessage(new HandleRef(null, hwnd), 1125, 0, flag ? 1 : 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
<Window x:Class="SourceGit.Views.FolderBrowser"
|
||||
x:Name="me"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:controls="clr-namespace:SourceGit.Views.Controls"
|
||||
mc:Ignorable="d"
|
||||
UseLayoutRounding="True"
|
||||
Title="{StaticResource Text.FolderDialog}"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
ResizeMode="NoResize"
|
||||
Height="400" Width="400">
|
||||
<WindowChrome.WindowChrome>
|
||||
<WindowChrome UseAeroCaptionButtons="False" CornerRadius="0" CaptionHeight="28" ResizeBorderThickness="1"/>
|
||||
</WindowChrome.WindowChrome>
|
||||
|
||||
<controls:WindowBorder>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="28"/>
|
||||
<RowDefinition Height="32"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="48"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Title Bar -->
|
||||
<Grid Grid.Row="0" Background="{StaticResource Brush.TitleBar}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- ICON -->
|
||||
<Path Grid.Column="0" Width="16" Height="16" Margin="6,0" Data="{StaticResource Icon.Folder.Open}"/>
|
||||
|
||||
<!-- Title -->
|
||||
<TextBlock Grid.Column="1" Text="{Binding ElementName=me, Path=Description}"/>
|
||||
|
||||
<!-- Close -->
|
||||
<controls:IconButton
|
||||
Grid.Column="3"
|
||||
Click="Quit"
|
||||
Width="28" Padding="8"
|
||||
WindowChrome.IsHitTestVisibleInChrome="True"
|
||||
Icon="{StaticResource Icon.Close}"
|
||||
HoverBackground="Red"/>
|
||||
</Grid>
|
||||
|
||||
<!-- Selected -->
|
||||
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="8,4">
|
||||
<TextBlock Text="{StaticResource Text.FolderDialog.Selected}"/>
|
||||
<TextBlock x:Name="txtSelected" Text="NONE" Foreground="{StaticResource Brush.FG2}" Margin="8,0,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- File System Tree -->
|
||||
<Border
|
||||
Grid.Row="2"
|
||||
Margin="8,0"
|
||||
Background="{StaticResource Brush.Contents}"
|
||||
BorderBrush="{StaticResource Brush.Border0}"
|
||||
BorderThickness="1">
|
||||
<controls:Tree x:Name="tree" FontFamily="Consolas" ItemsSource="{Binding ElementName=me, Path=Nodes}" SelectionChanged="OnTreeSelectionChanged">
|
||||
<controls:Tree.ItemContainerStyle>
|
||||
<Style TargetType="{x:Type controls:TreeItem}" BasedOn="{StaticResource Style.TreeItem}">
|
||||
<EventSetter Event="Expanded" Handler="OnTreeNodeExpanded"/>
|
||||
</Style>
|
||||
</controls:Tree.ItemContainerStyle>
|
||||
|
||||
<controls:Tree.ItemTemplate>
|
||||
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
|
||||
<StackPanel Orientation="Horizontal" Height="24">
|
||||
<Path x:Name="Icon" Width="14" Height="14" Fill="Goldenrod" Data="{StaticResource Icon.Folder.Fill}"/>
|
||||
<TextBlock Text="{Binding Name}" Margin="6,0,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<HierarchicalDataTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type controls:TreeItem}}, Path=IsExpanded}" Value="True">
|
||||
<Setter TargetName="Icon" Property="Data" Value="{StaticResource Icon.Folder.Open}"/>
|
||||
</DataTrigger>
|
||||
</HierarchicalDataTemplate.Triggers>
|
||||
</HierarchicalDataTemplate>
|
||||
</controls:Tree.ItemTemplate>
|
||||
</controls:Tree>
|
||||
</Border>
|
||||
|
||||
<!-- Options -->
|
||||
<Border Grid.Row="3">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<Button x:Name="btnSure" IsEnabled="False" Width="100" Height="32" Content="{StaticResource Text.Sure}" Background="{StaticResource Brush.Accent1}" BorderBrush="{StaticResource Brush.FG1}"/>
|
||||
<Button Click="Quit" Width="100" Height="32" Margin="8,0,0,0" Content="{StaticResource Text.Cancel}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</controls:WindowBorder>
|
||||
</Window>
|
|
@ -1,102 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
|
||||
namespace SourceGit.Views {
|
||||
|
||||
/// <summary>
|
||||
/// 目录选择对话框
|
||||
/// </summary>
|
||||
public partial class FolderBrowser : Window {
|
||||
|
||||
/// <summary>
|
||||
/// 目录树节点.
|
||||
/// </summary>
|
||||
public class Node : INotifyPropertyChanged {
|
||||
public string Name { get; set; }
|
||||
public string Path { get; set; }
|
||||
public ObservableCollection<Node> Children { get; set; }
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
public Node(string name, string path) {
|
||||
Name = name;
|
||||
Path = path;
|
||||
Children = new ObservableCollection<Node>();
|
||||
}
|
||||
|
||||
public void CollectChildren() {
|
||||
Children.Clear();
|
||||
|
||||
try {
|
||||
var dir = new DirectoryInfo(Path);
|
||||
var subs = dir.GetDirectories();
|
||||
|
||||
foreach (var sub in subs) {
|
||||
if ((sub.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) continue;
|
||||
Children.Add(new Node(sub.Name, sub.FullName));
|
||||
}
|
||||
} catch { }
|
||||
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Children"));
|
||||
}
|
||||
}
|
||||
|
||||
public string Description { get; set; }
|
||||
public ObservableCollection<Node> Nodes { get; set; }
|
||||
|
||||
public FolderBrowser(string description, Action<string> onOK) {
|
||||
Description = description;
|
||||
Nodes = new ObservableCollection<Node>();
|
||||
|
||||
var drives = DriveInfo.GetDrives();
|
||||
foreach (var d in drives) {
|
||||
var node = new Node(d.Name, d.Name);
|
||||
node.CollectChildren();
|
||||
Nodes.Add(node);
|
||||
}
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
btnSure.Click += (o, e) => {
|
||||
if (tree.Selected.Count == 0) return;
|
||||
var node = tree.Selected[0] as Node;
|
||||
onOK?.Invoke(node.Path);
|
||||
Close();
|
||||
};
|
||||
}
|
||||
|
||||
public static void Open(Window owner, string description, Action<string> onOK) {
|
||||
var dialog = new FolderBrowser(description, onOK);
|
||||
if (owner == null) dialog.Owner = Application.Current.MainWindow;
|
||||
else dialog.Owner = owner;
|
||||
dialog.ShowDialog();
|
||||
}
|
||||
|
||||
private void OnTreeNodeExpanded(object sender, RoutedEventArgs e) {
|
||||
var item = sender as Controls.TreeItem;
|
||||
if (item == null) return;
|
||||
|
||||
var node = item.DataContext as Node;
|
||||
if (node == null) return;
|
||||
|
||||
foreach (var c in node.Children) c.CollectChildren();
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnTreeSelectionChanged(object sender, RoutedEventArgs e) {
|
||||
if (tree.Selected.Count == 0) {
|
||||
btnSure.IsEnabled = false;
|
||||
txtSelected.Text = "NONE";
|
||||
} else {
|
||||
btnSure.IsEnabled = true;
|
||||
txtSelected.Text = (tree.Selected[0] as Node).Path;
|
||||
}
|
||||
}
|
||||
|
||||
private void Quit(object sender, RoutedEventArgs e) {
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -54,10 +54,11 @@ namespace SourceGit.Views.Popups {
|
|||
}
|
||||
|
||||
private void OnFolderSelectorClick(object sender, System.Windows.RoutedEventArgs e) {
|
||||
FolderBrowser.Open(null, App.Text("Clone.Folder.Placeholder"), path => {
|
||||
Folder = path;
|
||||
var dialog = new Controls.FolderDialog("Clone.Folder.Placeholder");
|
||||
if (dialog.ShowDialog() == true) {
|
||||
Folder = dialog.SelectedPath;
|
||||
txtFolder.GetBindingExpression(TextBox.TextProperty).UpdateTarget();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,10 +61,11 @@ namespace SourceGit.Views {
|
|||
}
|
||||
|
||||
private void SelectGitCloneDir(object sender, RoutedEventArgs e) {
|
||||
FolderBrowser.Open(this, App.Text("Preference.Dialog.GitDir"), path => {
|
||||
Models.Preference.Instance.Git.DefaultCloneDir = path;
|
||||
var dialog = new Controls.FolderDialog("Preference.Dialog.GitDir");
|
||||
if (dialog.ShowDialog() == true) {
|
||||
Models.Preference.Instance.Git.DefaultCloneDir = dialog.SelectedPath;
|
||||
txtGitCloneDir?.GetBindingExpression(TextBox.TextProperty).UpdateTarget();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectMergeTool(object sender, RoutedEventArgs e) {
|
||||
|
|
|
@ -342,9 +342,10 @@ namespace SourceGit.Views.Widgets {
|
|||
var saveToPatch = new MenuItem();
|
||||
saveToPatch.Header = App.Text("CommitCM.SaveAsPatch");
|
||||
saveToPatch.Click += (o, e) => {
|
||||
FolderBrowser.Open(null, "Save patch to ...", saveTo => {
|
||||
new Commands.FormatPatch(repo.Path, commit.SHA, saveTo).Exec();
|
||||
});
|
||||
var dialog = new Controls.FolderDialog("SaveFileTo");
|
||||
if (dialog.ShowDialog() == true) {
|
||||
new Commands.FormatPatch(repo.Path, commit.SHA, dialog.SelectedPath).Exec();
|
||||
}
|
||||
};
|
||||
menu.Items.Add(saveToPatch);
|
||||
menu.Items.Add(new Separator());
|
||||
|
|
|
@ -289,10 +289,11 @@ namespace SourceGit.Views.Widgets {
|
|||
saveAs.Header = App.Text("SaveAs");
|
||||
saveAs.IsEnabled = node.Type == Models.ObjectType.Blob;
|
||||
saveAs.Click += (obj, ev) => {
|
||||
FolderBrowser.Open(null, App.Text("SaveFileTo"), saveTo => {
|
||||
var full = Path.Combine(saveTo, Path.GetFileName(node.Path));
|
||||
var dialog = new Controls.FolderDialog("SaveFileTo");
|
||||
if (dialog.ShowDialog() == true) {
|
||||
var full = Path.Combine(dialog.SelectedPath, Path.GetFileName(node.Path));
|
||||
new Commands.SaveRevisionFile(repo, node.Path, sha, full).Exec();
|
||||
});
|
||||
}
|
||||
ev.Handled = true;
|
||||
};
|
||||
|
||||
|
|
|
@ -47,7 +47,8 @@ namespace SourceGit.Views.Widgets {
|
|||
|
||||
#region FUNC_EVENTS
|
||||
private void OnOpenClicked(object sender, RoutedEventArgs e) {
|
||||
FolderBrowser.Open(null, App.Text("Welcome.OpenOrInitDialog"), CheckAndOpen);
|
||||
var dialog = new Controls.FolderDialog("Welcome.OpenOrInitDialog");
|
||||
if (dialog.ShowDialog() == true) CheckAndOpen(dialog.SelectedPath);
|
||||
}
|
||||
|
||||
private void OnCloneClicked(object sender, RoutedEventArgs e) {
|
||||
|
|
Loading…
Reference in a new issue