Browse Source

back port managed dialog work from wasabi wallet.

pull/2777/head
Dan Walmsley 7 years ago
parent
commit
44803ef9dd
  1. 10
      Avalonia.sln
  2. 2
      build/ReactiveUI.props
  3. 2
      build/Rx.props
  4. 5
      src/Avalonia.Dialogs/Avalonia.Dialogs.csproj
  5. 29
      src/Avalonia.Dialogs/Internal/ByteSizeHelper.cs
  6. 27
      src/Avalonia.Dialogs/Internal/ChildFitter.cs
  7. 26
      src/Avalonia.Dialogs/Internal/FileSizeStringConverter.cs
  8. 170
      src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml
  9. 129
      src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml.cs
  10. 72
      src/Avalonia.Dialogs/Internal/ManagedFileChooserFilterViewModel.cs
  11. 108
      src/Avalonia.Dialogs/Internal/ManagedFileChooserItemViewModel.cs
  12. 8
      src/Avalonia.Dialogs/Internal/ManagedFileChooserNavigationItem.cs
  13. 134
      src/Avalonia.Dialogs/Internal/ManagedFileChooserSources.cs
  14. 532
      src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs
  15. 7
      src/Avalonia.Dialogs/Internal/ManagedFileDialog.xaml
  16. 24
      src/Avalonia.Dialogs/Internal/ManagedFileDialog.xaml.cs
  17. 24
      src/Avalonia.Dialogs/Internal/ResourceSelectorConverter.cs
  18. 81
      src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs

10
Avalonia.sln

@ -1,7 +1,7 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15 # Visual Studio Version 16
VisualStudioVersion = 15.0.27130.2027 VisualStudioVersion = 16.0.29102.190
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Base", "src\Avalonia.Base\Avalonia.Base.csproj", "{B09B78D8-9B26-48B0-9149-D64A2F120F3F}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Base", "src\Avalonia.Base\Avalonia.Base.csproj", "{B09B78D8-9B26-48B0-9149-D64A2F120F3F}"
EndProject EndProject
@ -197,9 +197,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PlatformSanityChecks", "sam
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.ReactiveUI.UnitTests", "tests\Avalonia.ReactiveUI.UnitTests\Avalonia.ReactiveUI.UnitTests.csproj", "{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.ReactiveUI.UnitTests", "tests\Avalonia.ReactiveUI.UnitTests\Avalonia.ReactiveUI.UnitTests.csproj", "{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Controls.DataGrid", "src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj", "{3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.DataGrid", "src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj", "{3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Build.Tasks", "src\Avalonia.Build.Tasks\Avalonia.Build.Tasks.csproj", "{BF28998D-072C-439A-AFBB-2FE5021241E0}" EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Dialogs", "src\Avalonia.Dialogs\Avalonia.Dialogs.csproj", "{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Dialogs", "src\Avalonia.Dialogs\Avalonia.Dialogs.csproj", "{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}"
EndProject EndProject
Global Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution GlobalSection(SharedMSBuildProjectFiles) = preSolution

2
build/ReactiveUI.props

@ -1,5 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup> <ItemGroup>
<PackageReference Include="reactiveui" Version="9.0.1" /> <PackageReference Include="reactiveui" Version="9.19.5" />
</ItemGroup> </ItemGroup>
</Project> </Project>

2
build/Rx.props

@ -1,5 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup> <ItemGroup>
<PackageReference Include="System.Reactive" Version="4.0.0" /> <PackageReference Include="System.Reactive" Version="4.1.5" />
</ItemGroup> </ItemGroup>
</Project> </Project>

5
src/Avalonia.Dialogs/Avalonia.Dialogs.csproj

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
@ -13,6 +13,9 @@
</EmbeddedResource> </EmbeddedResource>
</ItemGroup> </ItemGroup>
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\ReactiveUI.props" />
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" /> <ProjectReference Include="..\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" /> <ProjectReference Include="..\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />

29
src/Avalonia.Dialogs/Internal/ByteSizeHelper.cs

@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Avalonia.Dialogs.Internal
{
public static class ByteSizeHelper
{
private static readonly string[] Prefixes =
{
"B",
"KB",
"MB",
"GB",
"TB"
};
public static string ToString(long bytes)
{
var index = 0;
while (bytes >= 1000)
{
bytes /= 1000;
++index;
}
return $"{bytes:N} {Prefixes[index]}";
}
}
}

27
src/Avalonia.Dialogs/Internal/ChildFitter.cs

@ -1,20 +1,21 @@
using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Layout; using Avalonia.Layout;
namespace Avalonia.Dialogs.Internal namespace Avalonia.Dialogs.Internal
{ {
class ChildFitter : Decorator class ChildFitter : Decorator
{ {
protected override Size MeasureOverride(Size availableSize) protected override Size MeasureOverride(Size availableSize)
{ {
return new Size(0, 0); return new Size(0, 0);
} }
protected override Size ArrangeOverride(Size finalSize) protected override Size ArrangeOverride(Size finalSize)
{ {
Child.Measure(finalSize); Child.Measure(finalSize);
base.ArrangeOverride(finalSize); base.ArrangeOverride(finalSize);
return finalSize; return finalSize;
} }
} }
} }

26
src/Avalonia.Dialogs/Internal/FileSizeStringConverter.cs

@ -0,0 +1,26 @@
using Avalonia.Data.Converters;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
namespace Avalonia.Dialogs.Internal
{
public class FileSizeStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if(value is long size && size > 0)
{
return ByteSizeHelper.ToString(size);
}
return "";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

170
src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml

@ -4,71 +4,127 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:dialogs="clr-namespace:Avalonia.Dialogs.Internal" xmlns:dialogs="clr-namespace:Avalonia.Dialogs.Internal"
xmlns:internal="clr-namespace:Avalonia.Dialogs.Internal"> xmlns:internal="clr-namespace:Avalonia.Dialogs.Internal">
<UserControl.Resources> <UserControl.Resources>
<DrawingGroup x:Key="LevelUp"> <internal:FileSizeStringConverter x:Key="FileSizeConverter" />
<GeometryDrawing Brush="#00FFFFFF" Geometry="F1M16,16L0,16 0,0 16,0z" /> <DrawingGroup x:Key="LevelUp">
<GeometryDrawing Brush="#FFF6F6F6" Geometry="F1M14.5,0L6.39,0 5.39,2 2.504,2C1.677,2,1,2.673,1,3.5L1,10.582 1,10.586 1,15.414 3,13.414 3,16 7,16 7,13.414 9,15.414 9,13 14.5,13C15.327,13,16,12.327,16,11.5L16,1.5C16,0.673,15.327,0,14.5,0" /> <GeometryDrawing Brush="#00FFFFFF" Geometry="F1M16,16L0,16 0,0 16,0z" />
<GeometryDrawing Brush="#FFDCB679" Geometry="F1M14,3L7.508,3 8.008,2 8.012,2 14,2z M14.5,1L7.008,1 6.008,3 2.504,3C2.227,3,2,3.224,2,3.5L2,9.582 4.998,6.586 9,10.586 9,12 14.5,12C14.775,12,15,11.776,15,11.5L15,1.5C15,1.224,14.775,1,14.5,1" /> <GeometryDrawing Brush="#FFF6F6F6" Geometry="F1M14.5,0L6.39,0 5.39,2 2.504,2C1.677,2,1,2.673,1,3.5L1,10.582 1,10.586 1,15.414 3,13.414 3,16 7,16 7,13.414 9,15.414 9,13 14.5,13C15.327,13,16,12.327,16,11.5L16,1.5C16,0.673,15.327,0,14.5,0" />
<GeometryDrawing Brush="#FF00529C" Geometry="F1M8,11L5,8 2,11 2,13 4,11 4,15 6,15 6,11 8,13z" /> <GeometryDrawing Brush="#FFDCB679" Geometry="F1M14,3L7.508,3 8.008,2 8.012,2 14,2z M14.5,1L7.008,1 6.008,3 2.504,3C2.227,3,2,3.224,2,3.5L2,9.582 4.998,6.586 9,10.586 9,12 14.5,12C14.775,12,15,11.776,15,11.5L15,1.5C15,1.224,14.775,1,14.5,1" />
<GeometryDrawing Brush="#FFF0EFF1" Geometry="F1M8.0001,1.9996L7.5001,3.0006 14.0001,3.0006 14.0001,1.9996z" /> <GeometryDrawing Brush="#FF00529C" Geometry="F1M8,11L5,8 2,11 2,13 4,11 4,15 6,15 6,11 8,13z" />
</DrawingGroup> <GeometryDrawing Brush="#FFF0EFF1" Geometry="F1M8.0001,1.9996L7.5001,3.0006 14.0001,3.0006 14.0001,1.9996z" />
<dialogs:ResourceSelectorConverter x:Key="Icons"> </DrawingGroup>
<DrawingGroup x:Key="Icon_Folder"><GeometryDrawing Brush="#00FFFFFF" Geometry="F1M0,0L16,0 16,16 0,16z" /><GeometryDrawing Brush="#FFF6F6F6" Geometry="F1M1.5,1L9.61,1 10.61,3 13.496,3C14.323,3,14.996,3.673,14.996,4.5L14.996,12.5C14.996,13.327,14.323,14,13.496,14L1.5,14C0.673,14,0,13.327,0,12.5L0,2.5C0,1.673,0.673,1,1.5,1" /><GeometryDrawing Brush="#FFDCB67A" Geometry="F1M2,3L8.374,3 8.874,4 2,4z M13.496,4L10,4 9.992,4 8.992,2 1.5,2C1.225,2,1,2.224,1,2.5L1,12.5C1,12.776,1.225,13,1.5,13L13.496,13C13.773,13,13.996,12.776,13.996,12.5L13.996,4.5C13.996,4.224,13.773,4,13.496,4" /><GeometryDrawing Brush="#FFEFEFF0" Geometry="F1M2,3L8.374,3 8.874,4 2,4z" /></DrawingGroup> <dialogs:ResourceSelectorConverter x:Key="Icons">
<DrawingGroup x:Key="Icon_File"><GeometryDrawing Brush="#00FFFFFF" Geometry="F1M16,16L0,16 0,0 16,0z" /><GeometryDrawing Brush="#FFF6F6F6" Geometry="F1M4,15C3.03,15,2,14.299,2,13L2,3C2,1.701,3.03,1,4,1L10.061,1 14,4.556 14,13C14,13.97,13.299,15,12,15z" /><GeometryDrawing Brush="#FF9B4E96" Geometry="F1M12,13L4,13 4,3 9,3 9,6 12,6z M9.641,2L3.964,2C3.964,2,3,2,3,3L3,13C3,14,3.964,14,3.964,14L11.965,14C12.965,14,13,13,13,13L13,5z" /><GeometryDrawing Brush="#FFF0EFF1" Geometry="F1M4,3L9,3 9,6 12,6 12,13 4,13z" /></DrawingGroup> <DrawingGroup x:Key="Icon_Folder">
</dialogs:ResourceSelectorConverter> <GeometryDrawing Brush="#00FFFFFF" Geometry="F1M0,0L16,0 16,16 0,16z" />
</UserControl.Resources> <GeometryDrawing Brush="#FFF6F6F6" Geometry="F1M1.5,1L9.61,1 10.61,3 13.496,3C14.323,3,14.996,3.673,14.996,4.5L14.996,12.5C14.996,13.327,14.323,14,13.496,14L1.5,14C0.673,14,0,13.327,0,12.5L0,2.5C0,1.673,0.673,1,1.5,1" />
<UserControl.DataTemplates> <GeometryDrawing Brush="#FFDCB67A" Geometry="F1M2,3L8.374,3 8.874,4 2,4z M13.496,4L10,4 9.992,4 8.992,2 1.5,2C1.225,2,1,2.224,1,2.5L1,12.5C1,12.776,1.225,13,1.5,13L13.496,13C13.773,13,13.996,12.776,13.996,12.5L13.996,4.5C13.996,4.224,13.773,4,13.496,4" />
<DataTemplate DataType="internal:ManagedFileChooserItemViewModel"> <GeometryDrawing Brush="#FFEFEFF0" Geometry="F1M2,3L8.374,3 8.874,4 2,4z" />
<StackPanel Background="Transparent" Orientation="Horizontal" </DrawingGroup>
Cursor="Hand"> <DrawingGroup x:Key="Icon_File">
<GeometryDrawing Brush="#00FFFFFF" Geometry="F1M16,16L0,16 0,0 16,0z" />
<DrawingPresenter Width="16" Height="16" Drawing="{Binding IconKey, Converter={StaticResource Icons}}"/> <GeometryDrawing Brush="#FFF6F6F6" Geometry="F1M4,15C3.03,15,2,14.299,2,13L2,3C2,1.701,3.03,1,4,1L10.061,1 14,4.556 14,13C14,13.97,13.299,15,12,15z" />
<TextBlock Text="{Binding DisplayName}"/> <GeometryDrawing Brush="#FF9B4E96" Geometry="F1M12,13L4,13 4,3 9,3 9,6 12,6z M9.641,2L3.964,2C3.964,2,3,2,3,3L3,13C3,14,3.964,14,3.964,14L11.965,14C12.965,14,13,13,13,13L13,5z" />
</StackPanel> <GeometryDrawing Brush="#FFF0EFF1" Geometry="F1M4,3L9,3 9,6 12,6 12,13 4,13z" />
</DataTemplate> </DrawingGroup>
</UserControl.DataTemplates> </dialogs:ResourceSelectorConverter>
<DockPanel> </UserControl.Resources>
<DockPanel DockPanel.Dock="Top" Margin="5"> <DockPanel>
<DockPanel DockPanel.Dock="Top" Margin="5">
<dialogs:ChildFitter DockPanel.Dock="Right" Width="{Binding ElementName=Location, Path=Bounds.Height}"> <dialogs:ChildFitter DockPanel.Dock="Right" Width="{Binding ElementName=Location, Path=Bounds.Height}">
<Button Command="{Binding GoUp}" > <Button Command="{Binding GoUp}" >
<DrawingPresenter Drawing="{StaticResource LevelUp}" Stretch="Fill"/> <DrawingPresenter Drawing="{StaticResource LevelUp}" Stretch="Fill"/>
</Button>
</Button> </dialogs:ChildFitter>
</dialogs:ChildFitter> <TextBox x:Name="Location" Text="{Binding Location}" Margin="0 0 5 0">
<TextBox x:Name="Location" IsReadOnly="True" Text="{Binding Location}" Margin="0 0 5 0"/> <TextBox.KeyBindings>
</DockPanel> <KeyBinding Command="{Binding EnterLocationCommand}" Gesture="Enter"/>
<DockPanel DockPanel.Dock="Bottom"> </TextBox.KeyBindings>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Left"> </TextBox>
<CheckBox IsChecked="{Binding ShowHiddenFiles}"> </DockPanel>
<TextBlock>Show hidden files</TextBlock> <DockPanel DockPanel.Dock="Bottom">
</CheckBox> <StackPanel Orientation="Horizontal" DockPanel.Dock="Left">
</StackPanel> <CheckBox IsChecked="{Binding ShowHiddenFiles}">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right"> <TextBlock>Show hidden files</TextBlock>
<StackPanel.Styles> </CheckBox>
<Style Selector="Button"> </StackPanel>
<Setter Property="Margin">4</Setter> <StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
</Style> <StackPanel.Styles>
</StackPanel.Styles> <Style Selector="Button">
<Button Command="{Binding Ok}">OK</Button> <Setter Property="Margin">4</Setter>
<Button Command="{Binding Cancel}">Cancel</Button> </Style>
</StackPanel> </StackPanel.Styles>
</DockPanel> <Button Command="{Binding Ok}">OK</Button>
<DropDown DockPanel.Dock="Bottom" <Button Command="{Binding Cancel}">Cancel</Button>
</StackPanel>
</DockPanel>
<DropDown DockPanel.Dock="Bottom"
IsVisible="{Binding ShowFilters}" IsVisible="{Binding ShowFilters}"
Items="{Binding Filters}" Items="{Binding Filters}"
SelectedItem="{Binding SelectedFilter}" SelectedItem="{Binding SelectedFilter}"
/> />
<ListBox Margin="5" BorderBrush="Transparent" x:Name="QuickLinks" Items="{Binding QuickLinks}" <ListBox Margin="5" BorderBrush="Transparent" x:Name="QuickLinks" Items="{Binding QuickLinks}"
SelectedIndex="{Binding QuickLinksSelectedIndex}" SelectedIndex="{Binding QuickLinksSelectedIndex}"
DockPanel.Dock="Left" Background="{DynamicResource ThemeControlMidBrush}" Focusable="False" /> DockPanel.Dock="Left" Background="{DynamicResource ThemeControlMidBrush}" Focusable="False">
<ListBox x:Name="Files" <ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Spacing="4" Orientation="Horizontal" Background="Transparent">
<DrawingPresenter Width="16" Height="16" Drawing="{Binding IconKey, Converter={StaticResource Icons}}"/>
<TextBlock Text="{Binding DisplayName}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<DockPanel Grid.IsSharedSizeScope="True">
<Grid DockPanel.Dock="Top" Margin="15 0 0 0" HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20" />
<ColumnDefinition Width="16" />
<ColumnDefinition Width="400"/>
<ColumnDefinition Width="16" />
<ColumnDefinition Width="200" />
<ColumnDefinition Width="16" />
<ColumnDefinition Width="150" />
<ColumnDefinition Width="16"/>
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="2" Text="Name" />
<TextBlock Grid.Column="4" Text="Date Modified" />
<TextBlock Grid.Column="6" Text="Type" />
<TextBlock Grid.Column="8" Text="Size" />
</Grid>
<TextBox Text="{Binding FileName}" Watermark="File name" DockPanel.Dock="Bottom" IsVisible="{Binding !SelectingFolder}" />
<ListBox x:Name="Files"
VirtualizationMode="Simple" VirtualizationMode="Simple"
Items="{Binding Items}" Items="{Binding Items}"
Margin="5" Margin="5"
SelectionMode="{Binding SelectionMode}" SelectionMode="{Binding SelectionMode}"
SelectedItems="{Binding SelectedItems}"/> SelectedItems="{Binding SelectedItems}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Background="Transparent">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20" />
<ColumnDefinition Width="16" />
<ColumnDefinition Width="400" />
<ColumnDefinition Width="16" />
<ColumnDefinition Width="200" />
<ColumnDefinition Width="16" />
<ColumnDefinition Width="150" />
<ColumnDefinition Width="16"/>
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
<DrawingPresenter Width="16" Height="16" Drawing="{Binding IconKey, Converter={StaticResource Icons}}"/>
<TextBlock Grid.Column="2" Text="{Binding DisplayName}"/>
<TextBlock Grid.Column="4" Text="{Binding Modified}" />
<TextBlock Grid.Column="6" Text="{Binding Type}" />
<TextBlock Grid.Column="8" Text="{Binding Size, Converter={StaticResource FileSizeConverter}}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel> </DockPanel>
</DockPanel>
</UserControl> </UserControl>

129
src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml.cs

@ -1,6 +1,7 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
@ -9,57 +10,79 @@ using Avalonia.Markup.Xaml;
namespace Avalonia.Dialogs.Internal namespace Avalonia.Dialogs.Internal
{ {
class ManagedFileChooser : UserControl class ManagedFileChooser : UserControl
{ {
private Control _quickLinksRoot; private Control _quickLinksRoot;
private ListBox _filesView; private ListBox _filesView;
public ManagedFileChooser() public ManagedFileChooser()
{ {
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
AddHandler(PointerPressedEvent, OnPointerPressed, RoutingStrategies.Tunnel); AddHandler(PointerPressedEvent, OnPointerPressed, RoutingStrategies.Tunnel);
_quickLinksRoot = this.FindControl<Control>("QuickLinks"); _quickLinksRoot = this.FindControl<Control>("QuickLinks");
_filesView = this.FindControl<ListBox>("Files"); _filesView = this.FindControl<ListBox>("Files");
} }
ManagedFileChooserViewModel Model => DataContext as ManagedFileChooserViewModel; ManagedFileChooserViewModel Model => DataContext as ManagedFileChooserViewModel;
private void OnPointerPressed(object sender, PointerPressedEventArgs e) private void OnPointerPressed(object sender, PointerPressedEventArgs e)
{ {
var model = (e.Source as StyledElement)?.DataContext as ManagedFileChooserItemViewModel; var model = (e.Source as StyledElement)?.DataContext as ManagedFileChooserItemViewModel;
if(model == null)
return; if (model == null)
{
var isQuickLink = _quickLinksRoot.IsLogicalParentOf(e.Source as Control); return;
if (e.ClickCount == 2 || isQuickLink) }
{
if (model.IsDirectory) var isQuickLink = _quickLinksRoot.IsLogicalParentOf(e.Source as Control);
Model?.Navigate(model.Path); if (e.ClickCount == 2 || isQuickLink)
else {
Model?.SelectSingleFile(model); if (model.IsDirectory)
e.Handled = true; {
} Model?.Navigate(model.Path);
} }
else
protected override async void OnDataContextChanged(EventArgs e) {
{ Model?.SelectSingleFile(model);
base.OnDataContextChanged(e); }
var model = (DataContext as ManagedFileChooserViewModel);
if (model == null) e.Handled = true;
return; }
var preselected = model.SelectedItems.FirstOrDefault(); }
if(preselected == null)
return; protected override async void OnDataContextChanged(EventArgs e)
{
//Let everything to settle down and scroll to selected item base.OnDataContextChanged(e);
await Task.Delay(100);
if (preselected != model.SelectedItems.FirstOrDefault()) var model = (DataContext as ManagedFileChooserViewModel);
return;
if (model == null)
// Workaround for ListBox bug, scroll to the previous file {
var indexOfPreselected = model.Items.IndexOf(preselected); return;
if (indexOfPreselected > 1) }
_filesView.ScrollIntoView(model.Items[indexOfPreselected - 1]);
} var preselected = model.SelectedItems.FirstOrDefault();
}
if (preselected == null)
{
return;
}
//Let everything to settle down and scroll to selected item
await Task.Delay(100);
if (preselected != model.SelectedItems.FirstOrDefault())
{
return;
}
// Workaround for ListBox bug, scroll to the previous file
var indexOfPreselected = model.Items.IndexOf(preselected);
if (indexOfPreselected > 1)
{
_filesView.ScrollIntoView(model.Items[indexOfPreselected - 1]);
}
}
}
} }

72
src/Avalonia.Dialogs/Internal/ManagedFileChooserFilterViewModel.cs

@ -5,34 +5,46 @@ using Avalonia.Controls;
namespace Avalonia.Dialogs.Internal namespace Avalonia.Dialogs.Internal
{ {
class ManagedFileChooserFilterViewModel : InternalViewModelBase class ManagedFileChooserFilterViewModel : InternalViewModelBase
{ {
private readonly string[] _extensions; private readonly string[] _extensions;
public string Name { get; } public string Name { get; }
public ManagedFileChooserFilterViewModel(FileDialogFilter filter) public ManagedFileChooserFilterViewModel(FileDialogFilter filter)
{ {
Name = filter.Name; Name = filter.Name;
if (filter.Extensions.Contains("*"))
return; if (filter.Extensions.Contains("*"))
_extensions = filter.Extensions?.Select(e => "." + e.ToLowerInvariant()).ToArray(); {
} return;
}
public ManagedFileChooserFilterViewModel()
{ _extensions = filter.Extensions?.Select(e => "." + e.ToLowerInvariant()).ToArray();
Name = "All files"; }
}
public ManagedFileChooserFilterViewModel()
public bool Match(string filename) {
{ Name = "All files";
if (_extensions == null) }
return true;
foreach(var ext in _extensions) public bool Match(string filename)
if (filename.EndsWith(ext, StringComparison.InvariantCultureIgnoreCase)) {
return true; if (_extensions == null)
return false; {
} return true;
}
public override string ToString() => Name;
} foreach (var ext in _extensions)
{
if (filename.EndsWith(ext, StringComparison.InvariantCultureIgnoreCase))
{
return true;
}
}
return false;
}
public override string ToString() => Name;
}
} }

108
src/Avalonia.Dialogs/Internal/ManagedFileChooserItemViewModel.cs

@ -1,45 +1,69 @@
using System;
namespace Avalonia.Dialogs.Internal namespace Avalonia.Dialogs.Internal
{ {
class ManagedFileChooserItemViewModel : InternalViewModelBase class ManagedFileChooserItemViewModel : InternalViewModelBase
{ {
private string _displayName; private string _displayName;
private string _path; private string _path;
private bool _isDirectory; private bool _isDirectory;
private DateTime _modified;
public string DisplayName private string _type;
{ private long _size;
get => _displayName;
set => RaiseAndSetIfChanged(ref _displayName, value); public string DisplayName
} {
get => _displayName;
public string Path set => this.RaiseAndSetIfChanged(ref _displayName, value);
{ }
get => _path;
set => RaiseAndSetIfChanged(ref _path, value); public string Path
} {
get => _path;
public string IconKey => IsDirectory ? "Icon_Folder" : "Icon_File"; set => this.RaiseAndSetIfChanged(ref _path, value);
}
public bool IsDirectory
{ public DateTime Modified
get => _isDirectory; {
set get => _modified;
{ set => this.RaiseAndSetIfChanged(ref _modified, value);
if (RaiseAndSetIfChanged(ref _isDirectory, value)) }
RaisePropertyChanged(nameof(IconKey));
} public string Type
} {
get => _type;
public ManagedFileChooserItemViewModel() set => this.RaiseAndSetIfChanged(ref _type, value);
{ }
} public long Size
{
public ManagedFileChooserItemViewModel(ManagedFileChooserNavigationItem item) get => _size;
{ set => this.RaiseAndSetIfChanged(ref _size, value);
IsDirectory = true; }
Path = item.Path;
DisplayName = item.DisplayName; public string IconKey => IsDirectory ? "Icon_Folder" : "Icon_File";
}
} public bool IsDirectory
{
get => _isDirectory;
set
{
if (this.RaiseAndSetIfChanged(ref _isDirectory, value))
{
this.RaisePropertyChanged(nameof(IconKey));
}
}
}
public ManagedFileChooserItemViewModel()
{
}
public ManagedFileChooserItemViewModel(ManagedFileChooserNavigationItem item)
{
IsDirectory = true;
Path = item.Path;
DisplayName = item.DisplayName;
}
}
} }

8
src/Avalonia.Dialogs/Internal/ManagedFileChooserNavigationItem.cs

@ -0,0 +1,8 @@
namespace Avalonia.Dialogs.Internal
{
public class ManagedFileChooserNavigationItem
{
public string DisplayName { get; set; }
public string Path { get; set; }
}
}

134
src/Avalonia.Dialogs/Internal/ManagedFileChooserSources.cs

@ -5,71 +5,85 @@ using System.Runtime.InteropServices;
namespace Avalonia.Dialogs.Internal namespace Avalonia.Dialogs.Internal
{ {
public class ManagedFileChooserSources public class ManagedFileChooserSources
{ {
public Func<ManagedFileChooserNavigationItem[]> GetUserDirectories { get; set; } public Func<ManagedFileChooserNavigationItem[]> GetUserDirectories { get; set; }
= DefaultGetUserDirectories; = DefaultGetUserDirectories;
public Func<ManagedFileChooserNavigationItem[]> GetFileSystemRoots { get; set; } public Func<ManagedFileChooserNavigationItem[]> GetFileSystemRoots { get; set; }
= DefaultGetFileSystemRoots; = DefaultGetFileSystemRoots;
public Func<ManagedFileChooserSources, ManagedFileChooserNavigationItem[]> GetAllItemsDelegate { get; set; } public Func<ManagedFileChooserSources, ManagedFileChooserNavigationItem[]> GetAllItemsDelegate { get; set; }
= DefaultGetAllItems; = DefaultGetAllItems;
public ManagedFileChooserNavigationItem[] GetAllItems() => GetAllItemsDelegate(this); public ManagedFileChooserNavigationItem[] GetAllItems() => GetAllItemsDelegate(this);
public static ManagedFileChooserNavigationItem[] DefaultGetAllItems(ManagedFileChooserSources sources) public static ManagedFileChooserNavigationItem[] DefaultGetAllItems(ManagedFileChooserSources sources)
{ {
return sources.GetUserDirectories().Concat(sources.GetFileSystemRoots()).ToArray(); return sources.GetUserDirectories().Concat(sources.GetFileSystemRoots()).ToArray();
} }
private static Environment.SpecialFolder[] s_folders = new[]
{
Environment.SpecialFolder.Desktop,
Environment.SpecialFolder.UserProfile,
Environment.SpecialFolder.MyDocuments,
Environment.SpecialFolder.MyMusic,
Environment.SpecialFolder.MyPictures,
Environment.SpecialFolder.MyVideos
};
public static ManagedFileChooserNavigationItem[] DefaultGetUserDirectories()
{
return s_folders.Select(Environment.GetFolderPath).Distinct()
.Where(d => !string.IsNullOrWhiteSpace(d))
.Where(Directory.Exists)
.Select(d => new ManagedFileChooserNavigationItem
{
Path = d,
DisplayName = Path.GetFileName(d)
}).ToArray();
}
public static ManagedFileChooserNavigationItem[] DefaultGetFileSystemRoots() private static Environment.SpecialFolder[] s_folders = new[]
{ {
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) Environment.SpecialFolder.Desktop,
{ Environment.SpecialFolder.UserProfile,
return DriveInfo.GetDrives().Select(d => new ManagedFileChooserNavigationItem Environment.SpecialFolder.MyDocuments,
{ Environment.SpecialFolder.MyMusic,
DisplayName = d.Name, Environment.SpecialFolder.MyPictures,
Path = d.RootDirectory.FullName Environment.SpecialFolder.MyVideos
}).ToArray(); };
}
return new[] public static ManagedFileChooserNavigationItem[] DefaultGetUserDirectories()
{ {
new ManagedFileChooserNavigationItem return s_folders.Select(Environment.GetFolderPath).Distinct()
{ .Where(d => !string.IsNullOrWhiteSpace(d))
DisplayName = "File System", .Where(Directory.Exists)
Path = "/" .Select(d => new ManagedFileChooserNavigationItem
} {
}; Path = d,
} DisplayName = Path.GetFileName(d)
} }).ToArray();
}
public class ManagedFileChooserNavigationItem public static ManagedFileChooserNavigationItem[] DefaultGetFileSystemRoots()
{ {
public string DisplayName { get; set; } if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
public string Path { get; set; } {
} return DriveInfo.GetDrives().Select(d => new ManagedFileChooserNavigationItem
{
DisplayName = d.Name,
Path = d.RootDirectory.FullName
}).ToArray();
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
var paths = Directory.GetDirectories("/Volumes");
return paths.Select(x => new ManagedFileChooserNavigationItem
{
DisplayName = Path.GetFileName(x),
Path = x
}).ToArray();
}
else
{
var paths = Directory.GetDirectories("/media/");
var drives = new ManagedFileChooserNavigationItem[]
{
new ManagedFileChooserNavigationItem
{
DisplayName = "File System",
Path = "/"
}
}.Concat(paths.Select(x => new ManagedFileChooserNavigationItem
{
DisplayName = Path.GetFileName(x),
Path = x
})).ToArray();
return drives;
}
}
}
} }

532
src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs

@ -2,222 +2,334 @@ using System;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reactive;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Threading; using Avalonia.Threading;
using ReactiveUI;
namespace Avalonia.Dialogs.Internal namespace Avalonia.Dialogs.Internal
{ {
class ManagedFileChooserViewModel : InternalViewModelBase class ManagedFileChooserViewModel : InternalViewModelBase
{ {
public event Action CancelRequested; public event Action CancelRequested;
public event Action<string[]> CompleteRequested; public event Action<string[]> CompleteRequested;
public AvaloniaList<ManagedFileChooserItemViewModel> QuickLinks { get; } = public AvaloniaList<ManagedFileChooserItemViewModel> QuickLinks { get; } =
new AvaloniaList<ManagedFileChooserItemViewModel>(); new AvaloniaList<ManagedFileChooserItemViewModel>();
public AvaloniaList<ManagedFileChooserItemViewModel> Items { get; } = public AvaloniaList<ManagedFileChooserItemViewModel> Items { get; } =
new AvaloniaList<ManagedFileChooserItemViewModel>(); new AvaloniaList<ManagedFileChooserItemViewModel>();
public AvaloniaList<ManagedFileChooserFilterViewModel> Filters { get; } = public AvaloniaList<ManagedFileChooserFilterViewModel> Filters { get; } =
new AvaloniaList<ManagedFileChooserFilterViewModel>(); new AvaloniaList<ManagedFileChooserFilterViewModel>();
public AvaloniaList<ManagedFileChooserItemViewModel> SelectedItems { get; } = public AvaloniaList<ManagedFileChooserItemViewModel> SelectedItems { get; } =
new AvaloniaList<ManagedFileChooserItemViewModel>(); new AvaloniaList<ManagedFileChooserItemViewModel>();
string _location; string _location;
private bool _showHiddenFiles; string _fileName;
private ManagedFileChooserFilterViewModel _selectedFilter; private bool _showHiddenFiles;
private bool _selectingDirectory; private ManagedFileChooserFilterViewModel _selectedFilter;
private bool _scheduledSelectionValidation; private bool _selectingDirectory;
private bool _savingFile;
public string Location private bool _scheduledSelectionValidation;
{ private string _defaultExtension;
get => _location;
private set => RaiseAndSetIfChanged(ref _location, value); public string Location
} {
get => _location;
public bool ShowFilters { get; } private set => this.RaiseAndSetIfChanged(ref _location, value);
public SelectionMode SelectionMode { get; } }
public string Title { get; }
public string FileName
public int QuickLinksSelectedIndex {
{ get => _fileName;
get private set => this.RaiseAndSetIfChanged(ref _fileName, value);
{ }
for (var index = 0; index < QuickLinks.Count; index++)
{ public bool SelectingFolder => _selectingDirectory;
var i = QuickLinks[index];
if (i.Path == Location) public bool ShowFilters { get; }
return index; public SelectionMode SelectionMode { get; }
} public string Title { get; }
return -1; public int QuickLinksSelectedIndex
} {
set => RaisePropertyChanged(nameof(QuickLinksSelectedIndex)); get
} {
for (var index = 0; index < QuickLinks.Count; index++)
public ManagedFileChooserFilterViewModel SelectedFilter {
{ var i = QuickLinks[index];
get => _selectedFilter;
set if (i.Path == Location)
{ {
RaiseAndSetIfChanged(ref _selectedFilter, value); return index;
Refresh(); }
} }
}
return -1;
public bool ShowHiddenFiles }
{ set => this.RaisePropertyChanged(nameof(QuickLinksSelectedIndex));
get => _showHiddenFiles; }
set
{ public ManagedFileChooserFilterViewModel SelectedFilter
RaiseAndSetIfChanged(ref _showHiddenFiles, value); {
Refresh(); get => _selectedFilter;
} set
} {
this.RaiseAndSetIfChanged(ref _selectedFilter, value);
public ManagedFileChooserViewModel(FileSystemDialog dialog) Refresh();
{ }
var quickSources = AvaloniaLocator.Current.GetService<ManagedFileChooserSources>() }
?? new ManagedFileChooserSources();
QuickLinks.Clear(); public bool ShowHiddenFiles
{
QuickLinks.AddRange(quickSources.GetAllItems().Select(i => new ManagedFileChooserItemViewModel(i))); get => _showHiddenFiles;
Title = dialog.Title ?? ( set
dialog is OpenFileDialog ? "Open file" {
: dialog is SaveFileDialog ? "Save file" this.RaiseAndSetIfChanged(ref _showHiddenFiles, value);
: dialog is OpenFolderDialog ? "Select directory" Refresh();
: throw new ArgumentException(nameof(dialog))); }
}
var directory = dialog.Directory;
if (directory == null || !Directory.Exists(directory)) public ManagedFileChooserViewModel(FileSystemDialog dialog)
directory = Directory.GetCurrentDirectory(); {
var quickSources = AvaloniaLocator.Current.GetService<ManagedFileChooserSources>()
if (dialog is FileDialog fd) ?? new ManagedFileChooserSources();
{
if (fd.Filters?.Count > 0) QuickLinks.Clear();
{
Filters.AddRange(fd.Filters.Select(f => new ManagedFileChooserFilterViewModel(f))); QuickLinks.AddRange(quickSources.GetAllItems().Select(i => new ManagedFileChooserItemViewModel(i)));
_selectedFilter = Filters[0];
ShowFilters = true; Title = dialog.Title ?? (
} dialog is OpenFileDialog ? "Open file"
: dialog is SaveFileDialog ? "Save file"
if (dialog is OpenFileDialog ofd) : dialog is OpenFolderDialog ? "Select directory"
{ : throw new ArgumentException(nameof(dialog)));
if (ofd.AllowMultiple)
SelectionMode = SelectionMode.Multiple; var directory = dialog.InitialDirectory;
}
} if (directory == null || !Directory.Exists(directory))
{
_selectingDirectory = dialog is OpenFolderDialog; directory = Directory.GetCurrentDirectory();
}
Navigate(directory, (dialog as FileDialog)?.InitialFileName);
SelectedItems.CollectionChanged += OnSelectionChanged; if (dialog is FileDialog fd)
} {
if (fd.Filters?.Count > 0)
private async void OnSelectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
{ Filters.AddRange(fd.Filters.Select(f => new ManagedFileChooserFilterViewModel(f)));
if(_scheduledSelectionValidation) _selectedFilter = Filters[0];
return; ShowFilters = true;
_scheduledSelectionValidation = true; }
await Dispatcher.UIThread.InvokeAsync(() =>
{ if (dialog is OpenFileDialog ofd)
try {
{ if (ofd.AllowMultiple)
if(_selectingDirectory) {
SelectedItems.Clear(); SelectionMode = SelectionMode.Multiple;
else }
{ }
var invalidItems = SelectedItems.Where(i => i.IsDirectory).ToList(); }
foreach (var item in invalidItems)
SelectedItems.Remove(item); _selectingDirectory = dialog is OpenFolderDialog;
}
} if(dialog is SaveFileDialog sfd)
finally {
{ _savingFile = true;
_scheduledSelectionValidation = false; _defaultExtension = sfd.DefaultExtension;
} FileName = sfd.InitialFileName;
}); }
}
Navigate(directory, (dialog as FileDialog)?.InitialFileName);
void NavigateRoot(string initialSelectionName) SelectedItems.CollectionChanged += OnSelectionChangedAsync;
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) EnterLocationCommand = ReactiveCommand.Create(() =>
Navigate(Path.GetPathRoot(Environment.GetFolderPath(Environment.SpecialFolder.System)), initialSelectionName); {
else if (Directory.Exists(Location))
Navigate("/", initialSelectionName); {
} Navigate(Location);
}
public void Refresh() => Navigate(Location); else if (File.Exists(Location))
{
public void Navigate(string path, string initialSelectionName = null) CompleteRequested?.Invoke(new[] { Location });
{ }
if (!Directory.Exists(path)) });
NavigateRoot(initialSelectionName); }
else
{ private async void OnSelectionChangedAsync(object sender, NotifyCollectionChangedEventArgs e)
Location = path; {
Items.Clear(); if (_scheduledSelectionValidation)
SelectedItems.Clear(); {
var infos = new DirectoryInfo(path).EnumerateFileSystemInfos(); return;
if (!ShowHiddenFiles) }
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) _scheduledSelectionValidation = true;
infos = infos.Where(i => (i.Attributes & (FileAttributes.Hidden | FileAttributes.System)) != 0); await Dispatcher.UIThread.InvokeAsync(() =>
else {
infos = infos.Where(i => !i.Name.StartsWith(".")); try
} {
if (_selectingDirectory)
if (SelectedFilter != null) {
infos = infos.Where(i => i is DirectoryInfo || SelectedFilter.Match(i.Name)); SelectedItems.Clear();
}
Items.AddRange(infos.Select(info => new ManagedFileChooserItemViewModel else
{ {
DisplayName = info.Name, var invalidItems = SelectedItems.Where(i => i.IsDirectory).ToList();
Path = info.FullName, foreach (var item in invalidItems)
IsDirectory = info is DirectoryInfo {
}).OrderByDescending(x => x.IsDirectory) SelectedItems.Remove(item);
.ThenBy(x => x.DisplayName, StringComparer.InvariantCultureIgnoreCase)); }
if (initialSelectionName != null) if(!_selectingDirectory)
{ {
var sel = Items.FirstOrDefault(i => !i.IsDirectory && i.DisplayName == initialSelectionName); FileName = SelectedItems.FirstOrDefault()?.DisplayName;
if (sel != null) }
SelectedItems.Add(sel); }
} }
finally
RaisePropertyChanged(nameof(QuickLinksSelectedIndex)); {
} _scheduledSelectionValidation = false;
}
});
} }
public void GoUp() void NavigateRoot(string initialSelectionName)
{ {
var parent = Path.GetDirectoryName(Location); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
if (string.IsNullOrWhiteSpace(parent)) {
return; Navigate(Path.GetPathRoot(Environment.GetFolderPath(Environment.SpecialFolder.System)), initialSelectionName);
Navigate(parent); }
} else
{
public void Cancel() Navigate("/", initialSelectionName);
{ }
CancelRequested?.Invoke(); }
}
public void Refresh() => Navigate(Location);
public void Ok()
{ public void Navigate(string path, string initialSelectionName = null)
if (_selectingDirectory) {
CompleteRequested?.Invoke(new[] {Location}); if (!Directory.Exists(path))
else {
CompleteRequested?.Invoke(SelectedItems.Select(i => i.Path).ToArray()); NavigateRoot(initialSelectionName);
} }
else
public void SelectSingleFile(ManagedFileChooserItemViewModel item) {
{ Location = path;
CompleteRequested?.Invoke(new[] {item.Path}); Items.Clear();
} SelectedItems.Clear();
}
try
{
var infos = new DirectoryInfo(path).EnumerateFileSystemInfos();
if (!ShowHiddenFiles)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
infos = infos.Where(i => (i.Attributes & (FileAttributes.Hidden | FileAttributes.System)) != 0);
}
else
{
infos = infos.Where(i => !i.Name.StartsWith("."));
}
}
if (SelectedFilter != null)
{
infos = infos.Where(i => i is DirectoryInfo || SelectedFilter.Match(i.Name));
}
Items.AddRange(infos.Where(x =>
{
if (_selectingDirectory)
{
if (!(x is DirectoryInfo))
{
return false;
}
}
return true;
}).Select(info => new ManagedFileChooserItemViewModel
{
DisplayName = info.Name,
Path = info.FullName,
IsDirectory = info is DirectoryInfo,
Type = info is FileInfo ? info.Extension : "File Folder",
Size = info is FileInfo f ? f.Length : 0,
Modified = info.LastWriteTime
})
.OrderByDescending(x => x.IsDirectory)
.ThenBy(x => x.DisplayName, StringComparer.InvariantCultureIgnoreCase));
if (initialSelectionName != null)
{
var sel = Items.FirstOrDefault(i => !i.IsDirectory && i.DisplayName == initialSelectionName);
if (sel != null)
{
SelectedItems.Add(sel);
}
}
this.RaisePropertyChanged(nameof(QuickLinksSelectedIndex));
}
catch (System.UnauthorizedAccessException)
{
}
}
}
public void GoUp()
{
var parent = Path.GetDirectoryName(Location);
if (string.IsNullOrWhiteSpace(parent))
{
return;
}
Navigate(parent);
}
public void Cancel()
{
CancelRequested?.Invoke();
}
public void Ok()
{
if (_selectingDirectory)
{
CompleteRequested?.Invoke(new[] { Location });
}
else if(_savingFile)
{
if (!string.IsNullOrWhiteSpace(FileName))
{
if (!Path.HasExtension(FileName) && !string.IsNullOrWhiteSpace(_defaultExtension))
{
FileName = Path.ChangeExtension(FileName, _defaultExtension);
}
CompleteRequested?.Invoke(new[] { Path.Combine(Location, FileName) });
}
}
else
{
CompleteRequested?.Invoke(SelectedItems.Select(i => i.Path).ToArray());
}
}
public void SelectSingleFile(ManagedFileChooserItemViewModel item)
{
CompleteRequested?.Invoke(new[] { item.Path });
}
public ReactiveCommand<Unit, Unit> EnterLocationCommand { get; }
}
} }

7
src/Avalonia.Dialogs/Internal/ManagedFileDialog.xaml

@ -2,6 +2,9 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dialogs="clr-namespace:Avalonia.Dialogs.Internal" xmlns:dialogs="clr-namespace:Avalonia.Dialogs.Internal"
xmlns:internal="clr-namespace:Avalonia.Dialogs.Internal" xmlns:internal="clr-namespace:Avalonia.Dialogs.Internal"
Title="{Binding Title}" MinWidth="700" MinHeight="500"> Icon="resm:WalletWasabi.Gui.Assets.WasabiLogo256.png?assembly=WalletWasabi.Gui"
<internal:ManagedFileChooser Margin="4"/> Title="{Binding Title}" Width="1100" Height="500" MinWidth="1100" MinHeight="500"
FontFamily="{DynamicResource UiFont}" FontSize="14"
Foreground="{DynamicResource ThemeForegroundBrush}">
<internal:ManagedFileChooser Margin="12 0 12 6"/>
</Window> </Window>

24
src/Avalonia.Dialogs/Internal/ManagedFileDialog.xaml.cs

@ -1,18 +1,20 @@
using System; using System;
using System.Runtime.InteropServices;
using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Media;
namespace Avalonia.Dialogs.Internal namespace Avalonia.Dialogs.Internal
{ {
class ManagedFileDialog : Window class ManagedFileDialog : Window
{ {
private ManagedFileChooserViewModel _model; public ManagedFileDialog()
public ManagedFileDialog() {
{ AvaloniaXamlLoader.Load(this);
AvaloniaXamlLoader.Load(this); #if DEBUG
#if DEBUG this.AttachDevTools();
this.AttachDevTools(); #endif
#endif }
} }
}
} }

24
src/Avalonia.Dialogs/Internal/ResourceSelectorConverter.cs

@ -5,17 +5,17 @@ using Avalonia.Data.Converters;
namespace Avalonia.Dialogs.Internal namespace Avalonia.Dialogs.Internal
{ {
public class ResourceSelectorConverter : ResourceDictionary, IValueConverter public class ResourceSelectorConverter : ResourceDictionary, IValueConverter
{ {
public object Convert(object key, Type targetType, object parameter, CultureInfo culture) public object Convert(object key, Type targetType, object parameter, CultureInfo culture)
{ {
TryGetResource((string)key, out var value); TryGetResource((string)key, out var value);
return value; return value;
} }
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
} }
} }

81
src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs

@ -1,5 +1,6 @@
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
using Avalonia.Dialogs.Internal; using Avalonia.Dialogs.Internal;
@ -7,48 +8,48 @@ using Avalonia.Platform;
namespace Avalonia.Dialogs namespace Avalonia.Dialogs
{ {
public static class ManagedFileDialogExtensions public static class ManagedFileDialogExtensions
{ {
class ManagedSystemDialogImpl : ISystemDialogImpl class ManagedSystemDialogImpl : ISystemDialogImpl
{ {
async Task<string[]> Show(SystemDialog d, IWindowImpl parent) async Task<string[]> Show(SystemDialog d, IWindowImpl parent)
{ {
var model = new ManagedFileChooserViewModel((FileSystemDialog)d); var model = new ManagedFileChooserViewModel((FileSystemDialog)d);
var dialog = new ManagedFileDialog
{
DataContext = model
};
string[] result = null; var dialog = new ManagedFileDialog
model.CompleteRequested += items => {
{ DataContext = model
result = items; };
dialog.Close();
};
model.CancelRequested += dialog.Close;
await dialog.ShowDialog<object>(parent); string[] result = null;
return result; model.CompleteRequested += items =>
} {
result = items;
public async Task<string[]> ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) dialog.Close();
{ };
return await Show(dialog, parent); model.CancelRequested += dialog.Close;
}
public async Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) await dialog.ShowDialog<object>(parent);
{ return result;
return (await Show(dialog, parent))?.FirstOrDefault(); }
}
}
public static TAppBuilder UseManagedSystemDialogs<TAppBuilder>(this TAppBuilder builder) public async Task<string[]> ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent)
where TAppBuilder : AppBuilderBase<TAppBuilder>, new() {
{ return await Show(dialog, parent);
builder.AfterPlatformServicesSetup(_ => }
AvaloniaLocator.CurrentMutable.Bind<ISystemDialogImpl>().ToSingleton<ManagedSystemDialogImpl>());
return builder; public async Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent)
} {
} return (await Show(dialog, parent))?.FirstOrDefault();
}
}
public static TAppBuilder UseManagedSystemDialogs<TAppBuilder>(this TAppBuilder builder)
where TAppBuilder : AppBuilderBase<TAppBuilder>, new()
{
builder.AfterSetup(_ =>
AvaloniaLocator.CurrentMutable.Bind<ISystemDialogImpl>().ToSingleton<ManagedSystemDialogImpl>());
return builder;
}
}
} }

Loading…
Cancel
Save