diff --git a/.gitignore b/.gitignore index 940794e6..3432f1fb 100644 --- a/.gitignore +++ b/.gitignore @@ -286,3 +286,6 @@ __pycache__/ *.btm.cs *.odx.cs *.xsd.cs + +# directory of local nuget package builds +packageBuilds/ diff --git a/LICENSE b/LICENSE index 6db15b21..927f52c3 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2017-2018 Philipp Spiegel +Copyright (c) 2017-2020 Philipp Spiegel Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/MaterialDesignExtensions.sln b/MaterialDesignExtensions.sln index 58660b8b..8178ea17 100644 --- a/MaterialDesignExtensions.sln +++ b/MaterialDesignExtensions.sln @@ -1,32 +1,56 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27130.2026 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29613.14 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MaterialDesignExtensions", "MaterialDesignExtensions\MaterialDesignExtensions.csproj", "{809632DA-5EB8-4EE8-AD71-57239701550C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MaterialDesignExtensions", "MaterialDesignExtensions\MaterialDesignExtensions.csproj", "{809632DA-5EB8-4EE8-AD71-57239701550C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MaterialDesignExtensionsDemo", "MaterialDesignExtensionsDemo\MaterialDesignExtensionsDemo.csproj", "{279D7E62-1206-4928-890E-00F51CD0181C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MaterialDesignExtensionsDemo", "MaterialDesignExtensionsDemo\MaterialDesignExtensionsDemo.csproj", "{279D7E62-1206-4928-890E-00F51CD0181C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MaterialDesignExtensionsTests", "MaterialDesignExtensionsTests\MaterialDesignExtensionsTests.csproj", "{DCF16057-4955-48F1-BD0E-28D671DF59B0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MaterialDesignExtensionsTests", "MaterialDesignExtensionsTests\MaterialDesignExtensionsTests.csproj", "{DCF16057-4955-48F1-BD0E-28D671DF59B0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MaterialDesignExtensionsBuildUtility", "MaterialDesignExtensionsBuildUtility\MaterialDesignExtensionsBuildUtility.csproj", "{2E40D411-CE99-489E-A1E4-9F28A1399CA1}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + DebugLongPath|Any CPU = DebugLongPath|Any CPU Release|Any CPU = Release|Any CPU + ReleaseLongPath|Any CPU = ReleaseLongPath|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {809632DA-5EB8-4EE8-AD71-57239701550C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {809632DA-5EB8-4EE8-AD71-57239701550C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {809632DA-5EB8-4EE8-AD71-57239701550C}.DebugLongPath|Any CPU.ActiveCfg = Debug|Any CPU + {809632DA-5EB8-4EE8-AD71-57239701550C}.DebugLongPath|Any CPU.Build.0 = Debug|Any CPU {809632DA-5EB8-4EE8-AD71-57239701550C}.Release|Any CPU.ActiveCfg = Release|Any CPU {809632DA-5EB8-4EE8-AD71-57239701550C}.Release|Any CPU.Build.0 = Release|Any CPU + {809632DA-5EB8-4EE8-AD71-57239701550C}.ReleaseLongPath|Any CPU.ActiveCfg = Release|Any CPU + {809632DA-5EB8-4EE8-AD71-57239701550C}.ReleaseLongPath|Any CPU.Build.0 = Release|Any CPU {279D7E62-1206-4928-890E-00F51CD0181C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {279D7E62-1206-4928-890E-00F51CD0181C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {279D7E62-1206-4928-890E-00F51CD0181C}.DebugLongPath|Any CPU.ActiveCfg = Debug|Any CPU + {279D7E62-1206-4928-890E-00F51CD0181C}.DebugLongPath|Any CPU.Build.0 = Debug|Any CPU {279D7E62-1206-4928-890E-00F51CD0181C}.Release|Any CPU.ActiveCfg = Release|Any CPU {279D7E62-1206-4928-890E-00F51CD0181C}.Release|Any CPU.Build.0 = Release|Any CPU + {279D7E62-1206-4928-890E-00F51CD0181C}.ReleaseLongPath|Any CPU.ActiveCfg = Release|Any CPU + {279D7E62-1206-4928-890E-00F51CD0181C}.ReleaseLongPath|Any CPU.Build.0 = Release|Any CPU {DCF16057-4955-48F1-BD0E-28D671DF59B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DCF16057-4955-48F1-BD0E-28D671DF59B0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DCF16057-4955-48F1-BD0E-28D671DF59B0}.DebugLongPath|Any CPU.ActiveCfg = Debug|Any CPU + {DCF16057-4955-48F1-BD0E-28D671DF59B0}.DebugLongPath|Any CPU.Build.0 = Debug|Any CPU {DCF16057-4955-48F1-BD0E-28D671DF59B0}.Release|Any CPU.ActiveCfg = Release|Any CPU {DCF16057-4955-48F1-BD0E-28D671DF59B0}.Release|Any CPU.Build.0 = Release|Any CPU + {DCF16057-4955-48F1-BD0E-28D671DF59B0}.ReleaseLongPath|Any CPU.ActiveCfg = Release|Any CPU + {DCF16057-4955-48F1-BD0E-28D671DF59B0}.ReleaseLongPath|Any CPU.Build.0 = Release|Any CPU + {2E40D411-CE99-489E-A1E4-9F28A1399CA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2E40D411-CE99-489E-A1E4-9F28A1399CA1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2E40D411-CE99-489E-A1E4-9F28A1399CA1}.DebugLongPath|Any CPU.ActiveCfg = Debug|Any CPU + {2E40D411-CE99-489E-A1E4-9F28A1399CA1}.DebugLongPath|Any CPU.Build.0 = Debug|Any CPU + {2E40D411-CE99-489E-A1E4-9F28A1399CA1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2E40D411-CE99-489E-A1E4-9F28A1399CA1}.Release|Any CPU.Build.0 = Release|Any CPU + {2E40D411-CE99-489E-A1E4-9F28A1399CA1}.ReleaseLongPath|Any CPU.ActiveCfg = Debug|Any CPU + {2E40D411-CE99-489E-A1E4-9F28A1399CA1}.ReleaseLongPath|Any CPU.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/MaterialDesignExtensions/Commands/Internal/AutocompleteCommands.cs b/MaterialDesignExtensions/Commands/Internal/AutocompleteCommands.cs new file mode 100644 index 00000000..824b15c3 --- /dev/null +++ b/MaterialDesignExtensions/Commands/Internal/AutocompleteCommands.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; + +namespace MaterialDesignExtensions.Commands.Internal +{ + /// + /// Class containing commands used by the for internal use only. + /// + public class AutocompleteCommands + { + // abstract class with private constructor to prevent object initialization + private AutocompleteCommands() { } + + /// + /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. + /// + public static readonly RoutedCommand SelectAutocompleteItemCommand = new RoutedCommand(); + } +} diff --git a/MaterialDesignExtensions/Commands/Internal/FileSystemControlCommands.cs b/MaterialDesignExtensions/Commands/Internal/FileSystemControlCommands.cs new file mode 100644 index 00000000..f18d0124 --- /dev/null +++ b/MaterialDesignExtensions/Commands/Internal/FileSystemControlCommands.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; + +namespace MaterialDesignExtensions.Commands.Internal +{ + /// + /// Class containing commands used by the file system controls for internal use only. + /// + public abstract class FileSystemControlCommands + { + // abstract class with private constructor to prevent object initialization + private FileSystemControlCommands() { } + + /// + /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. + /// + public static readonly RoutedCommand CreateNewDirectoryCommand = new RoutedCommand(); + + /// + /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. + /// + public static readonly RoutedCommand CancelCommand = new RoutedCommand(); + + /// + /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. + /// + public static readonly RoutedCommand CancelNewDirectoryCommand = new RoutedCommand(); + + /// + /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. + /// + public static readonly RoutedCommand FileSystemEntryDoubleClickCommand = new RoutedCommand(); + + /// + /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. + /// + public static readonly RoutedCommand OpenSelectionDrawerCommand = new RoutedCommand(); + + /// + /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. + /// + public static readonly RoutedCommand OpenSpecialDirectoriesDrawerCommand = new RoutedCommand(); + + /// + /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. + /// + public static readonly RoutedCommand SelectDirectoryCommand = new RoutedCommand(); + + /// + /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. + /// + public static readonly RoutedCommand SelectDirectoryItemCommand = new RoutedCommand(); + + /// + /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. + /// + public static readonly RoutedCommand SelectFileCommand = new RoutedCommand(); + + /// + /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. + /// + public static readonly RoutedCommand SelectFileSystemEntryCommand = new RoutedCommand(); + + /// + /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. + /// + public static readonly RoutedCommand SelectMultipleDirectoriesCommand = new RoutedCommand(); + + /// + /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. + /// + public static readonly RoutedCommand SelectMultipleFilesCommand = new RoutedCommand(); + + /// + /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. + /// + public static readonly RoutedCommand ShowCreateNewDirectoryCommand = new RoutedCommand(); + + /// + /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. + /// + public static readonly RoutedCommand ShowInfoCommand = new RoutedCommand(); + + /// + /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. + /// + public static readonly RoutedCommand SwitchPathPartsAsButtonsCommand = new RoutedCommand(); + } +} diff --git a/MaterialDesignExtensions/Commands/Internal/OversizedNumberSpinnerCommands.cs b/MaterialDesignExtensions/Commands/Internal/OversizedNumberSpinnerCommands.cs new file mode 100644 index 00000000..a3fdfb2f --- /dev/null +++ b/MaterialDesignExtensions/Commands/Internal/OversizedNumberSpinnerCommands.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; + +namespace MaterialDesignExtensions.Commands.Internal +{ + /// + /// Class containing commands used by the for internal use only. + /// + public class OversizedNumberSpinnerCommands + { + // abstract class with private constructor to prevent object initialization + private OversizedNumberSpinnerCommands() { } + + /// + /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. + /// + public static readonly RoutedCommand EditValueCommand = new RoutedCommand(); + + /// + /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. + /// + public static readonly RoutedCommand MinusCommand = new RoutedCommand(); + + /// + /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. + /// + public static readonly RoutedCommand PlusCommand = new RoutedCommand(); + } +} diff --git a/MaterialDesignExtensions/Commands/Internal/SearchControlCommands.cs b/MaterialDesignExtensions/Commands/Internal/SearchControlCommands.cs new file mode 100644 index 00000000..50c5458a --- /dev/null +++ b/MaterialDesignExtensions/Commands/Internal/SearchControlCommands.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; + +namespace MaterialDesignExtensions.Commands.Internal +{ + /// + /// Class containing commands used by the search controls for internal use only. + /// + public class SearchControlCommands + { + + // abstract class with private constructor to prevent object initialization + private SearchControlCommands() { } + + /// + /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. + /// + public static readonly RoutedCommand SelectSearchSuggestionCommand = new RoutedCommand(); + } +} diff --git a/MaterialDesignExtensions/Commands/Internal/SideNavigationCommands.cs b/MaterialDesignExtensions/Commands/Internal/SideNavigationCommands.cs new file mode 100644 index 00000000..dff382a6 --- /dev/null +++ b/MaterialDesignExtensions/Commands/Internal/SideNavigationCommands.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; + +namespace MaterialDesignExtensions.Commands.Internal +{ + /// + /// Class containing commands used by the for internal use only. + /// + public class SideNavigationCommands + { + // abstract class with private constructor to prevent object initialization + private SideNavigationCommands() { } + + /// + /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. + /// + public static readonly RoutedCommand SelectNavigationItemCommand = new RoutedCommand(); + } +} diff --git a/MaterialDesignExtensions/Commands/Internal/StepperCommands.cs b/MaterialDesignExtensions/Commands/Internal/StepperCommands.cs new file mode 100644 index 00000000..6d35e478 --- /dev/null +++ b/MaterialDesignExtensions/Commands/Internal/StepperCommands.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; + +namespace MaterialDesignExtensions.Commands.Internal +{ + /// + /// Class containing commands used by the for internal use only. + /// + public class StepperCommands + { + // abstract class with private constructor to prevent object initialization + private StepperCommands() { } + + /// + /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. + /// + public static readonly RoutedCommand BackCommand = new RoutedCommand(); + + /// + /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. + /// + public static readonly RoutedCommand CancelCommand = new RoutedCommand(); + + /// + /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. + /// + public static readonly RoutedCommand ContinueCommand = new RoutedCommand(); + + /// + /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. + /// + public static readonly RoutedCommand StepSelectedCommand = new RoutedCommand(); + } +} diff --git a/MaterialDesignExtensions/Commands/Internal/TextBoxSuggestionsCommands.cs b/MaterialDesignExtensions/Commands/Internal/TextBoxSuggestionsCommands.cs new file mode 100644 index 00000000..65f096bc --- /dev/null +++ b/MaterialDesignExtensions/Commands/Internal/TextBoxSuggestionsCommands.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; + +namespace MaterialDesignExtensions.Commands.Internal +{ + /// + /// Class containing commands used by the for internal use only. + /// + public class TextBoxSuggestionsCommands + { + // abstract class with private constructor to prevent object initialization + private TextBoxSuggestionsCommands() { } + + /// + /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. + /// + public static readonly RoutedCommand SelectSuggestionItemCommand = new RoutedCommand(); + } +} diff --git a/MaterialDesignExtensions/Controllers/AutocompleteController.cs b/MaterialDesignExtensions/Controllers/AutocompleteController.cs index 5ea03b73..4a0ac091 100644 --- a/MaterialDesignExtensions/Controllers/AutocompleteController.cs +++ b/MaterialDesignExtensions/Controllers/AutocompleteController.cs @@ -9,6 +9,9 @@ namespace MaterialDesignExtensions.Controllers { + /// + /// The controller with the technical logic for autocomplete controls. + /// public class AutocompleteController { private readonly object m_lockObject = new object(); @@ -77,44 +80,45 @@ public AutocompleteController() } /// - /// Starts a new task for the autocomplete and propagates the result via the event. + /// Starts a new task for the autocomplete and propagates the result via the event. /// This method does some technical stuff and delegates the actual search to the . /// /// The term to search for public void Search(string searchTerm) { - string id = Guid.NewGuid().ToString(); - - IAutocompleteSource autocompleteSource = null; - - lock (m_lockObject) + Task.Run(async () => { - autocompleteSource = m_autocompleteSource; + string id = Guid.NewGuid().ToString(); + + IAutocompleteSource autocompleteSource = null; - // no source, no search - if (autocompleteSource == null) + lock (m_lockObject) { - return; + autocompleteSource = m_autocompleteSource; + + // no source, no search + if (autocompleteSource == null) + { + return; + } + + LastId = id; } - LastId = id; - } + await Task.Delay(SearchDelay).ConfigureAwait(false); - Task.Delay(SearchDelay) - .ContinueWith((prevTask) => + // search only if there was no other request during the delay + if (DoSearchWithId(id)) { - // search only if there was no other request during the delay + IEnumerable items = autocompleteSource.Search(searchTerm); + + // a final check if this result will not be replaced by another active search if (DoSearchWithId(id)) { - IEnumerable items = autocompleteSource.Search(searchTerm); - - // a final check if this result will not be replaced by another active search - if (DoSearchWithId(id)) - { - AutocompleteItemsChanged?.Invoke(this, new AutocompleteItemsChangedEventArgs(items)); - } + AutocompleteItemsChanged?.Invoke(this, new AutocompleteItemsChangedEventArgs(items)); } - }); + } + }); } private bool DoSearchWithId(string id) diff --git a/MaterialDesignExtensions/Controllers/BitmapImageHelper.cs b/MaterialDesignExtensions/Controllers/BitmapImageHelper.cs index ba70f722..06a8b8f1 100644 --- a/MaterialDesignExtensions/Controllers/BitmapImageHelper.cs +++ b/MaterialDesignExtensions/Controllers/BitmapImageHelper.cs @@ -50,50 +50,71 @@ public static BitmapImage LoadImage(string imageFilename, int targetWidth = 40, try { - using (BufferedStream fileStream = new BufferedStream(new FileStream(imageFilename, FileMode.Open), BufferSize)) + using (MemoryStream imageMemoryStream = new MemoryStream()) { - BitmapDecoder bitmapDecoder = BitmapDecoder.Create(fileStream, BitmapCreateOptions.DelayCreation, BitmapCacheOption.Default); - int width = bitmapDecoder.Frames[0].PixelWidth; - int height = bitmapDecoder.Frames[0].PixelHeight; - - fileStream.Position = 0; + using (BufferedStream fileStream = new BufferedStream(new FileStream(imageFilename, FileMode.Open), BufferSize)) + { + fileStream.CopyTo(imageMemoryStream); + } - image = new BitmapImage(); - image.BeginInit(); + return GetAsBitmapImage(imageMemoryStream, targetWidth, targetHeight, useCache, key); + } + } + catch (Exception exc) + { + if (exc is IOException || exc is UnauthorizedAccessException || exc is PathTooLongException || exc is NotSupportedException) + { + return null; + } + else + { + throw; + } + } + } - double targetRatio = ((double)targetWidth) / targetHeight; - double ratio = ((double)width) / height; + /// + /// Loads the image from the file with the specified width and height keeping its ratio. + /// + /// + /// + /// + /// + /// + public static async Task LoadImageAsync(string imageFilename, int targetWidth = 40, int targetHeight = 40, bool useCache = false) + { + string key = GetKey(imageFilename, targetWidth, targetHeight); - if (targetRatio > ratio) - { - image.DecodePixelWidth = targetWidth; - } - else - { - image.DecodePixelHeight = targetHeight; - } + BitmapImage image = null; - image.CacheOption = BitmapCacheOption.OnLoad; - image.StreamSource = fileStream; + if (useCache) + { + lock (m_staticLockObject) + { + Cache.TryGetValue(key, out image); + } - image.EndInit(); - image.StreamSource = null; - image.Freeze(); + if (image != null) + { + return image; + } + } - if (useCache) + try + { + using (MemoryStream imageMemoryStream = new MemoryStream()) + { + using (BufferedStream fileStream = new BufferedStream(new FileStream(imageFilename, FileMode.Open), BufferSize)) { - lock (m_staticLockObject) - { - Cache[key] = image; - } + await fileStream.CopyToAsync(imageMemoryStream).ConfigureAwait(false); } - return image; + return GetAsBitmapImage(imageMemoryStream, targetWidth, targetHeight, useCache, key); } } catch (Exception exc) { - if (exc is IOException || exc is UnauthorizedAccessException || exc is PathTooLongException) + if (exc is IOException || exc is UnauthorizedAccessException || exc is PathTooLongException || exc is NotSupportedException) { return null; } @@ -109,6 +130,52 @@ private static string GetKey(string imageFilename, int targetWidth, int targetHe return string.Format("{0}_{1}_file://{2}", targetWidth, targetHeight, imageFilename); } + private static BitmapImage GetAsBitmapImage(MemoryStream imageMemoryStream, int targetWidth, int targetHeight, bool useCache, string key) + { + imageMemoryStream.Position = 0; + + BitmapDecoder bitmapDecoder = BitmapDecoder.Create(imageMemoryStream, BitmapCreateOptions.DelayCreation, BitmapCacheOption.Default); + int width = bitmapDecoder.Frames[0].PixelWidth; + int height = bitmapDecoder.Frames[0].PixelHeight; + + imageMemoryStream.Position = 0; + + BitmapImage image = new BitmapImage(); + image.BeginInit(); + + double targetRatio = ((double)targetWidth) / targetHeight; + double ratio = ((double)width) / height; + + if (targetRatio > ratio) + { + image.DecodePixelWidth = targetWidth; + } + else + { + image.DecodePixelHeight = targetHeight; + } + + image.CacheOption = BitmapCacheOption.OnLoad; + image.StreamSource = imageMemoryStream; + + image.EndInit(); + image.StreamSource = null; + image.Freeze(); + + if (useCache) + { + lock (m_staticLockObject) + { + Cache[key] = image; + } + } + + return image; + } + + /// + /// Clears the cache of loaded images. + /// public static void ClearCache() { lock (m_staticLockObject) diff --git a/MaterialDesignExtensions/Controllers/FileControlHelper.cs b/MaterialDesignExtensions/Controllers/FileControlHelper.cs new file mode 100644 index 00000000..58ac85ff --- /dev/null +++ b/MaterialDesignExtensions/Controllers/FileControlHelper.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; + +using MaterialDesignExtensions.Model; + +// use Pri.LongPath classes instead of System.IO for the MaterialDesignExtensions.LongPath build to support long file system paths on older Windows and .NET versions +#if LONG_PATH +using DirectoryInfo = Pri.LongPath.DirectoryInfo; +using FileInfo = Pri.LongPath.FileInfo; +using FileSystemInfo = Pri.LongPath.FileSystemInfo; +#endif + +namespace MaterialDesignExtensions.Controllers +{ + /// + /// Helper class for file system controls with general code for reuse. + /// + public abstract class FileControlHelper + { + private FileControlHelper() { } + + /// + /// Updates the visibility of the ComboBox with the filter options. + /// + /// + public static void UpdateFileFiltersVisibility(ComboBox fileFiltersComboBox) + { + if (fileFiltersComboBox != null + && fileFiltersComboBox.ItemsSource != null + && fileFiltersComboBox.ItemsSource.GetEnumerator().MoveNext()) + { + fileFiltersComboBox.Visibility = Visibility.Visible; + } + else + { + fileFiltersComboBox.Visibility = Visibility.Collapsed; + } + } + + /// + /// Builds a list with the items of a directory. + /// + /// + /// + /// + /// + /// + public static IEnumerable GetFileSystemEntryItems(List directoriesAndFiles, FileSystemController controller, bool groupFoldersAndFiles, Func isSelectedFunc) + { + if (directoriesAndFiles == null || !directoriesAndFiles.Any()) + { + return new ArrayList(0); + } + + int numberOfItems = directoriesAndFiles.Count; + + if (groupFoldersAndFiles) + { + numberOfItems = numberOfItems + 2; + } + + ArrayList items = new ArrayList(numberOfItems); + + for (int i = 0; i < directoriesAndFiles.Count; i++) + { + FileSystemInfo item = directoriesAndFiles[i]; + + if (item is DirectoryInfo directoryInfo) + { + if (groupFoldersAndFiles && i == 0) + { + items.Add(new FileSystemEntriesGroupHeader() { Header = Localization.Strings.Folders, ShowSeparator = false }); + } + + bool isSelected = directoryInfo.FullName == controller.CurrentDirectory?.FullName; + + items.Add(new DirectoryInfoItem() { IsSelected = isSelected, Value = directoryInfo }); + } + else if (item is FileInfo fileInfo) + { + if (groupFoldersAndFiles) + { + if (i == 0) + { + items.Add(new FileSystemEntriesGroupHeader() { Header = Localization.Strings.Files, ShowSeparator = false }); + } + else if (directoriesAndFiles[i - 1] is DirectoryInfo) + { + items.Add(new FileSystemEntriesGroupHeader() { Header = Localization.Strings.Files, ShowSeparator = true }); + } + } + + items.Add(new FileInfoItem() { IsSelected = isSelectedFunc(fileInfo), Value = fileInfo }); + } + } + + return items; + } + } +} diff --git a/MaterialDesignExtensions/Controllers/FileFilterHelper.cs b/MaterialDesignExtensions/Controllers/FileFilterHelper.cs index 8195f8a2..feaecacf 100644 --- a/MaterialDesignExtensions/Controllers/FileFilterHelper.cs +++ b/MaterialDesignExtensions/Controllers/FileFilterHelper.cs @@ -150,5 +150,26 @@ public static string ConvertFileFiltersToString(IEnumerable fileFil return sb.ToString(); } + + /// + /// Extracts the file extensions out of the file filter. + /// + /// + /// + public static IEnumerable GetFileExtensionsFromFilter(IFileFilter fileFilter) + { + if (fileFilter == null) + { + return null; + } + + string[] split = fileFilter.Filters.Split(';'); + + return fileFilter.Filters.Split(';') + .Select(filterStr => filterStr.Trim()) + .Where(filterStr => filterStr.Length > 1 && filterStr.Contains(".") && !filterStr.EndsWith(".")) + .Select(filterStr => filterStr.Substring(filterStr.LastIndexOf(".") + 1).Replace("*", string.Empty)) + .Where(fileExtension => !string.IsNullOrWhiteSpace(fileExtension)); + } } } diff --git a/MaterialDesignExtensions/Controllers/FileNameHelper.cs b/MaterialDesignExtensions/Controllers/FileNameHelper.cs new file mode 100644 index 00000000..041fc42a --- /dev/null +++ b/MaterialDesignExtensions/Controllers/FileNameHelper.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MaterialDesignExtensions.Controllers +{ + /// + /// Helper class for operations on file names. + /// + public abstract class FileNameHelper + { + private static readonly ISet NotAllowedChars = new HashSet() { '<', '>', ':', '"', '/', '\\', '|', '?', '*' }; + + // abstract class with private constructor to prevent object initialization + private FileNameHelper() { } + + /// + /// Checks the specified file name if it is OK for the operation system. + /// + /// The file name to check + /// boolean indication if teh file name is OK or not + public static bool CheckFileName(string fileName) + { + // see https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file#file-and-directory-names for file name constraints + + if (string.IsNullOrWhiteSpace(fileName)) + { + return false; + } + + for (int i = 0; i < fileName.Length; i++) + { + char c = fileName[i]; + ISet NotAllowedChars = new HashSet() { '<', '>', ':', '"', '/', '\\', '|', '?', '*' }; + + if (NotAllowedChars.Contains(c) || ((int)c) <= 31) + { + return false; + } + } + + return true; + } + } +} diff --git a/MaterialDesignExtensions/Controllers/FileSystemController.cs b/MaterialDesignExtensions/Controllers/FileSystemController.cs index 82172ca4..67d62eca 100644 --- a/MaterialDesignExtensions/Controllers/FileSystemController.cs +++ b/MaterialDesignExtensions/Controllers/FileSystemController.cs @@ -11,6 +11,13 @@ using MaterialDesignExtensions.Model; +// use Pri.LongPath classes instead of System.IO for the MaterialDesignExtensions.LongPath build to support long file system paths on older Windows and .NET versions +#if LONG_PATH +using FileSystemInfo = Pri.LongPath.FileSystemInfo; +using DirectoryInfo = Pri.LongPath.DirectoryInfo; +using FileInfo = Pri.LongPath.FileInfo; +#endif + namespace MaterialDesignExtensions.Controllers { /// @@ -33,6 +40,10 @@ public class FileSystemController : INotifyPropertyChanged private bool m_showSystemFilesAndDirectories; private IList m_fileFilters; private IFileFilter m_fileFilterToApply; + private bool m_forceFileExtensionOfFileFilter; + + private HashSet m_selectedDirectories; + private HashSet m_selectedFiles; /// /// The current directory shown in the control. @@ -179,7 +190,7 @@ public List Drives if (driveInfo.DriveType == DriveType.CDRom) { - icon = PackIconKind.Disk; + icon = PackIconKind.Disc; } else if (driveInfo.DriveType == DriveType.Removable) { @@ -197,7 +208,16 @@ public List Drives label = label.Substring(0, label.Length - 1); } - string volumeLabel = driveInfo.IsReady ? driveInfo.VolumeLabel : null; + string volumeLabel = null; + + try + { + volumeLabel = driveInfo.IsReady ? driveInfo.VolumeLabel : null; + } + catch (Exception) + { + return new SpecialDrive { Info = driveInfo, Icon = icon }; + } if (string.IsNullOrWhiteSpace(volumeLabel) && driveInfo.DriveType == DriveType.Fixed) { @@ -278,6 +298,49 @@ public IFileFilter FileFilterToApply } } + /// + /// Forces the possible file extension of the selected file filter for new filenames. + /// + public bool ForceFileExtensionOfFileFilter + { + get + { + return m_forceFileExtensionOfFileFilter; + } + + set + { + if (m_forceFileExtensionOfFileFilter != value) + { + m_forceFileExtensionOfFileFilter = value; + + OnPropertyChanged(nameof(ForceFileExtensionOfFileFilter)); + } + } + } + + /// + /// The selected directories for multiple selection controls. + /// + public HashSet SelectedDirectories + { + get + { + return m_selectedDirectories; + } + } + + /// + /// The selected files for multiple selection controls. + /// + public HashSet SelectedFiles + { + get + { + return m_selectedFiles; + } + } + /// /// The special directories (e.g. music directory) of the user. /// @@ -315,7 +378,7 @@ public bool ShowHiddenFilesAndDirectories set { - if (!m_showHiddenFilesAndDirectories != value) + if (m_showHiddenFilesAndDirectories != value) { m_showHiddenFilesAndDirectories = value; @@ -360,6 +423,10 @@ public FileSystemController() m_showSystemFilesAndDirectories = false; m_fileFilters = null; m_fileFilterToApply = null; + m_forceFileExtensionOfFileFilter = false; + + m_selectedDirectories = new HashSet(); + m_selectedFiles = new HashSet(); } /// @@ -437,6 +504,74 @@ bool ShowFileSystemInfo(FileSystemInfo fileSystemInfo) UpdateCurrentDirectoryPathParts(); } + /// + /// Selects (not yetselected) or removes (already selected so remove it) a directory for the multiple selection feature. + /// + /// + public void SelectOrRemoveDirectoryForMultipleSelection(string directory) + { + SelectOrRemoveDirectoryForMultipleSelection(new DirectoryInfo(directory)); + } + + /// + /// Selects (not yetselected) or removes (already selected so remove it) a directory for the multiple selection feature. + /// + /// + public void SelectOrRemoveDirectoryForMultipleSelection(DirectoryInfo directory) + { + IEnumerable sameDirectories = m_selectedDirectories + .Where(directoryInfo => directoryInfo.FullName.ToLower() == directory.FullName.ToLower()) + .ToList(); + + if (sameDirectories.Any()) + { + foreach (DirectoryInfo sameDirectory in sameDirectories) + { + m_selectedDirectories.Remove(sameDirectory); + } + } + else + { + m_selectedDirectories.Add(directory); + } + + OnPropertyChanged(nameof(SelectedDirectories)); + } + + /// + /// Selects (not yet selected) or removes (already selected so remove it) a file for the multiple selection feature. + /// + /// + public void SelectOrRemoveFileForMultipleSelection(string file) + { + SelectOrRemoveFileForMultipleSelection(new FileInfo(file)); + } + + /// + /// Selects (not yet selected) or removes (already selected so remove it) a file for the multiple selection feature. + /// + /// + public void SelectOrRemoveFileForMultipleSelection(FileInfo file) + { + IEnumerable sameFiles = m_selectedFiles + .Where(fileInfo => fileInfo.FullName.ToLower() == file.FullName.ToLower()) + .ToList(); + + if (sameFiles.Any()) + { + foreach (FileInfo sameFile in sameFiles) + { + m_selectedFiles.Remove(sameFile); + } + } + else + { + m_selectedFiles.Add(file); + } + + OnPropertyChanged(nameof(SelectedFiles)); + } + /// /// Selects a file. /// @@ -484,7 +619,7 @@ private void UpdateCurrentDirectoryPathParts() currentDirectoryPathParts.Add(directoryInfo); directoryInfo = directoryInfo.Parent; } - + currentDirectoryPathParts.Sort((directoryInfo1, directoryInfo2) => directoryInfo1.FullName.CompareTo(directoryInfo2.FullName)); } @@ -546,6 +681,85 @@ public void SetFileFilter(IList fileFilters, IFileFilter fileFilter } } + /// + /// Creates a new directory with the specified name. + /// + /// The name of the new directory + public void CreateNewDirectory(string newDirectoryName) + { + if (string.IsNullOrWhiteSpace(newDirectoryName)) + { + throw new ArgumentException(Localization.Strings.TheDirectoryNameMustNotBeEmpty); + } + + if (!FileNameHelper.CheckFileName(newDirectoryName)) + { + throw new ArgumentException(Localization.Strings.TheDirectoryNameIsInvalid); + } + + string fullDirectoryName = CurrentDirectory.FullName + @"\" + newDirectoryName; + DirectoryInfo newDirectory = new DirectoryInfo(fullDirectoryName); + + if (newDirectory.Exists) + { + throw new ArgumentException(string.Format(Localization.Strings.TheDirectoryXAlreadyExists, newDirectoryName)); + } + + // create directory and select it + newDirectory.Create(); + + // important: create a new DirectoryInfo instance, because the Exists property will not be updated by the Create() method + SelectDirectory(new DirectoryInfo(fullDirectoryName)); + } + + /// + /// Builds a full filename for the specified filename inside the current directory. + /// + /// The filename to append to the current directory + /// + public string BuildFullFileNameForInCurrentDirectory(string newFilename) + { + string filename = null; + + if (!string.IsNullOrWhiteSpace(newFilename) && m_currentDirectory != null) + { + string directory = m_currentDirectory.FullName; + + if (directory != null && !directory.EndsWith(@"\") && !directory.EndsWith("/")) + { + directory = $@"{directory}\"; + } + + filename = directory + newFilename.Trim(); + + // ensure that the full filename has a file extension out of the selected file filter + if (m_forceFileExtensionOfFileFilter && m_fileFilterToApply != null && !m_fileFilterToApply.IsMatch(filename)) + { + IEnumerable fileExtensions = FileFilterHelper.GetFileExtensionsFromFilter(m_fileFilterToApply); + + if (fileExtensions != null && fileExtensions.Any()) + { + fileExtensions = fileExtensions.Select(fileExtension => fileExtension.ToLower()); + string lowerCaseFilename = filename.ToLower(); + + bool hasWrongFileExtension = !fileExtensions.Any(fileExtension => lowerCaseFilename.EndsWith($".{fileExtension}")); + + if (hasWrongFileExtension) + { + if (!filename.EndsWith(".")) + { + filename = $"{filename}."; + } + + filename = filename + fileExtensions.First(); + } + } + } + } + + return filename; + } + private bool AreObjectsEqual(object o1, object o2) { if (o1 == o2) diff --git a/MaterialDesignExtensions/Controllers/StepperController.cs b/MaterialDesignExtensions/Controllers/StepperController.cs index 82963290..e6a73a61 100644 --- a/MaterialDesignExtensions/Controllers/StepperController.cs +++ b/MaterialDesignExtensions/Controllers/StepperController.cs @@ -196,7 +196,7 @@ public void InitSteps(IStep[] steps) } } - private int GetActiveStepIndex() + internal int GetActiveStepIndex() { if (m_stepViewModels != null) { diff --git a/MaterialDesignExtensions/Controls/AlertDialog.cs b/MaterialDesignExtensions/Controls/AlertDialog.cs new file mode 100644 index 00000000..5e905bbf --- /dev/null +++ b/MaterialDesignExtensions/Controls/AlertDialog.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; + +using MaterialDesignThemes.Wpf; + +namespace MaterialDesignExtensions.Controls +{ + /// + /// An alert dialog to show a title and message with an OK button only. + /// + public class AlertDialog : MessageDialog + { + static AlertDialog() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(AlertDialog), new FrameworkPropertyMetadata(typeof(AlertDialog))); + } + + /// + /// Creates a new . + /// + public AlertDialog() : base() { } + + /// + /// Shows a new . + /// + /// The name of the + /// The arguments for the dialog initialization + /// + public static async Task ShowDialogAsync(string dialogHostName, AlertDialogArguments args) + { + AlertDialog dialog = InitDialog(args); + + await DialogHost.Show(dialog, dialogHostName, args.OpenedHandler, args.ClosingHandler); + } + + /// + /// Shows a new . + /// + /// The + /// The arguments for the dialog initialization + /// + public static async Task ShowDialogAsync(DialogHost dialogHost, AlertDialogArguments args) + { + AlertDialog dialog = InitDialog(args); + + await dialogHost.ShowDialog(dialog, args.OpenedHandler, args.ClosingHandler); + } + + private static AlertDialog InitDialog(AlertDialogArguments args) + { + AlertDialog dialog = new AlertDialog + { + Title = args.Title, + Message = args.Message, + CustomContent = args.CustomContent, + CustomContentTemplate = args.CustomContentTemplate + }; + + if (!string.IsNullOrWhiteSpace(args.OkButtonLabel)) + { + dialog.OkButtonLabel = args.OkButtonLabel; + } + + return dialog; + } + } + + /// + /// The arguments for an alert dialog. + /// + public class AlertDialogArguments : MessageDialogArguments + { + /// + /// Creates a new . + /// + public AlertDialogArguments() : base() { } + } +} diff --git a/MaterialDesignExtensions/Controls/AppBar.cs b/MaterialDesignExtensions/Controls/AppBar.cs index d510f30a..c53b09fa 100644 --- a/MaterialDesignExtensions/Controls/AppBar.cs +++ b/MaterialDesignExtensions/Controls/AppBar.cs @@ -16,7 +16,7 @@ namespace MaterialDesignExtensions.Controls /// A custom control implementing the concept of the app bar (https://material.io/design/components/app-bars-top.html#usage). /// It provides an optional toggle button for a navigation drawer, an icon, a title and a content area (to add toolbar buttons for example). /// - [ContentProperty(nameof(Children))] + [ContentProperty(nameof(ContentAreaContent))] public class AppBar : Control { private const string BackButtonName = "backButton"; @@ -133,24 +133,46 @@ public ICommand BackCommand } /// - /// The items for the content area (toolbar buttons for example). + /// The content for the content area (toolbar buttons for example). /// - public static readonly DependencyPropertyKey ChildrenProperty = DependencyProperty.RegisterReadOnly( - nameof(Children), typeof(IList), typeof(AppBar), new UIPropertyMetadata(null, null)); + public static readonly DependencyProperty ContentAreaContentProperty = DependencyProperty.Register( + nameof(ContentAreaContent), typeof(object), typeof(AppBar), new UIPropertyMetadata(null, null)); /// - /// The items for the content area (toolbar buttons for example). + /// The content for the content area (toolbar buttons for example). /// - public IList Children + public object ContentAreaContent { get { - return (IList)GetValue(ChildrenProperty.DependencyProperty); + return GetValue(ContentAreaContentProperty); } set { - SetValue(ChildrenProperty, value); + SetValue(ContentAreaContentProperty, value); + } + } + + /// + /// The data template for the content area. + /// + public static readonly DependencyProperty ContentAreaContentTemplateProperty = DependencyProperty.Register( + nameof(ContentAreaContentTemplate), typeof(DataTemplate), typeof(AppBar), new UIPropertyMetadata(null, null)); + + /// + /// The data template for the content area. + /// + public object ContentAreaContentTemplate + { + get + { + return GetValue(ContentAreaContentTemplateProperty); + } + + set + { + SetValue(ContentAreaContentTemplateProperty, value); } } @@ -344,8 +366,6 @@ public AppBar() : base() { m_backButton = null; - - Children = new ObservableCollection(); } public override void OnApplyTemplate() diff --git a/MaterialDesignExtensions/Controls/Autocomplete.cs b/MaterialDesignExtensions/Controls/Autocomplete.cs index 00a4c4ae..6065afe4 100644 --- a/MaterialDesignExtensions/Controls/Autocomplete.cs +++ b/MaterialDesignExtensions/Controls/Autocomplete.cs @@ -9,6 +9,7 @@ using System.Windows.Input; using System.Windows.Controls.Primitives; +using MaterialDesignExtensions.Commands.Internal; using MaterialDesignExtensions.Controllers; using MaterialDesignExtensions.Model; @@ -24,11 +25,6 @@ public class Autocomplete : ControlWithAutocompletePopup private static readonly string ClearButtonName = "clearButton"; private static readonly string SearchTextBoxName = "searchTextBox"; - /// - /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. - /// - public static readonly RoutedCommand SelectAutocompleteItemCommand = new RoutedCommand(); - /// /// An event raised by changing the selected item. /// @@ -272,7 +268,7 @@ public Autocomplete() m_autocompleteController = new AutocompleteController() { AutocompleteSource = AutocompleteSource }; - CommandBindings.Add(new CommandBinding(SelectAutocompleteItemCommand, SelectAutocompleteItemCommandHandler)); + CommandBindings.Add(new CommandBinding(AutocompleteCommands.SelectAutocompleteItemCommand, SelectAutocompleteItemCommandHandler)); } public override void OnApplyTemplate() @@ -363,8 +359,7 @@ private void SearchTextBoxKeyUpHandler(object sender, KeyEventArgs args) private void ClearClickHandler(object sender, RoutedEventArgs args) { - SelectedItem = null; - SearchTerm = null; + ClearSelection(); m_searchTextBox.Focus(); } @@ -442,14 +437,17 @@ private void SelectAutocompleteItemCommandHandler(object sender, ExecutedRoutedE SelectedItem = args.Parameter; } - public void AutocompleteSourceItemsChangedHandler(object sender, AutocompleteSourceItemsChangedEventArgs args) + private void AutocompleteSourceItemsChangedHandler(object sender, AutocompleteSourceItemsChangedEventArgs args) { - if (m_searchTextBox != null && m_searchTextBox.IsKeyboardFocused) + Dispatcher.Invoke(() => { - m_autocompleteController?.Search(SearchTerm); + if (m_searchTextBox != null && m_searchTextBox.IsKeyboardFocused) + { + m_autocompleteController?.Search(SearchTerm); - UpdateClearButtonVisibility(); - } + UpdateClearButtonVisibility(); + } + }); } private void AutocompleteItemsChangedHandler(object sender, AutocompleteItemsChangedEventArgs args) @@ -489,6 +487,15 @@ private void UpdateClearButtonVisibility() } } } + + /// + /// Clears the selection. + /// + public void ClearSelection() + { + SelectedItem = null; + SearchTerm = null; + } } /// diff --git a/MaterialDesignExtensions/Controls/AutocompletePopup.cs b/MaterialDesignExtensions/Controls/AutocompletePopup.cs index 7c803e23..2b399f73 100644 --- a/MaterialDesignExtensions/Controls/AutocompletePopup.cs +++ b/MaterialDesignExtensions/Controls/AutocompletePopup.cs @@ -10,10 +10,16 @@ namespace MaterialDesignExtensions.Controls { + /// + /// Another implementation of to add a better behavior while moving or resizing the window. + /// public class AutocompletePopup : Popup { private Window m_window; + /// + /// Creates a new . + /// public AutocompletePopup() : base() { diff --git a/MaterialDesignExtensions/Controls/BaseFileControl.cs b/MaterialDesignExtensions/Controls/BaseFileControl.cs index 51e5dd91..81c0b064 100644 --- a/MaterialDesignExtensions/Controls/BaseFileControl.cs +++ b/MaterialDesignExtensions/Controls/BaseFileControl.cs @@ -10,10 +10,18 @@ using System.Windows.Controls; using System.Windows.Input; +using MaterialDesignExtensions.Commands.Internal; using MaterialDesignExtensions.Controllers; using MaterialDesignExtensions.Converters; using MaterialDesignExtensions.Model; +// use Pri.LongPath classes instead of System.IO for the MaterialDesignExtensions.LongPath build to support long file system paths on older Windows and .NET versions +#if LONG_PATH +using FileSystemInfo = Pri.LongPath.FileSystemInfo; +using DirectoryInfo = Pri.LongPath.DirectoryInfo; +using FileInfo = Pri.LongPath.FileInfo; +#endif + namespace MaterialDesignExtensions.Controls { /// @@ -21,12 +29,10 @@ namespace MaterialDesignExtensions.Controls /// public abstract class BaseFileControl : FileSystemControl { - protected const string FileFiltersComboBoxName = "fileFiltersComboBox"; - /// - /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. + /// The name of the combo box inside the template. /// - public static readonly RoutedCommand SelectFileCommand = new RoutedCommand(); + protected const string FileFiltersComboBoxName = "fileFiltersComboBox"; /// /// An event raised by selecting a file. @@ -56,7 +62,7 @@ public event RoutedEventHandler FileSelected public static readonly DependencyProperty ClearCacheOnUnloadProperty = DependencyProperty.Register( nameof(ClearCacheOnUnload), typeof(bool), - typeof(FileSystemControl), + typeof(BaseFileControl), new PropertyMetadata(true)); /// @@ -101,13 +107,13 @@ public string CurrentFile } /// - /// An command called by by selecting a file. + /// An command called by selecting a file. /// public static readonly DependencyProperty FileSelectedCommandProperty = DependencyProperty.Register( nameof(FileSelectedCommand), typeof(ICommand), typeof(BaseFileControl), new PropertyMetadata(null, null)); /// - /// An command called by by selecting a file. + /// An command called by selecting a file. /// public ICommand FileSelectedCommand { @@ -204,12 +210,16 @@ public bool GroupFoldersAndFiles protected ComboBox m_fileFiltersComboBox; + /// + /// Creates a new . + /// public BaseFileControl() : base() { m_fileFiltersComboBox = null; - CommandBindings.Add(new CommandBinding(SelectFileCommand, SelectFileCommandHandler)); + CommandBindings.Add(new CommandBinding(FileSystemControlCommands.SelectFileCommand, SelectFileCommandHandler)); + CommandBindings.Add(new CommandBinding(FileSystemControlCommands.FileSystemEntryDoubleClickCommand, FileDoubleClickCommandHandler)); } public override void OnApplyTemplate() @@ -224,12 +234,29 @@ public override void OnApplyTemplate() protected override void UnloadedHandler(object sender, RoutedEventArgs args) { - BitmapImageHelper.ClearCache(); + if (ClearCacheOnUnload) + { + BitmapImageHelper.ClearCache(); + } base.UnloadedHandler(sender, args); } + protected override void SelectFileSystemEntryCommandHandler(object sender, ExecutedRoutedEventArgs args) + { + SelectFileSystemEntry(args.Parameter as FileSystemInfo); + + Keyboard.Focus(this); + } + + protected abstract void SelectFileSystemEntry(FileSystemInfo fileSystemInfo); + protected virtual void SelectFileCommandHandler(object sender, ExecutedRoutedEventArgs args) + { + SelectFile(); + } + + protected virtual void SelectFile() { try { @@ -247,6 +274,22 @@ protected virtual void SelectFileCommandHandler(object sender, ExecutedRoutedEve } } + protected virtual void FileDoubleClickCommandHandler(object sender, ExecutedRoutedEventArgs args) + { + try + { + if (args.Parameter is FileInfo fileInfo) + { + SelectFileSystemEntry(fileInfo); + SelectFile(); + } + } + catch (PathTooLongException) + { + SnackbarMessageQueue.Enqueue(Localization.Strings.LongPathsAreNotSupported); + } + } + private static void CurrentFileChangedHandler(DependencyObject obj, DependencyPropertyChangedEventArgs args) { (obj as BaseFileControl)?.CurrentFileChangedHandler(args.NewValue as string); @@ -353,70 +396,17 @@ protected override IEnumerable GetFileSystemEntryItems() protected IEnumerable GetFileSystemEntryItems(List directoriesAndFiles) { - if (directoriesAndFiles == null || !directoriesAndFiles.Any()) - { - return new ArrayList(0); - } - - int numberOfItems = directoriesAndFiles.Count; - - if (GroupFoldersAndFiles) - { - numberOfItems = numberOfItems + 2; - } - - ArrayList items = new ArrayList(numberOfItems); - - for (int i = 0; i < directoriesAndFiles.Count; i++) - { - FileSystemInfo item = directoriesAndFiles[i]; - - if (item is DirectoryInfo directoryInfo) - { - if (GroupFoldersAndFiles && i == 0) - { - items.Add(new FileSystemEntriesGroupHeader() { Header = Localization.Strings.Folders, ShowSeparator = false }); - } - - bool isSelected = directoryInfo.FullName == m_controller.CurrentDirectory?.FullName; - - items.Add(new DirectoryInfoItem() { IsSelected = isSelected, Value = directoryInfo }); - } - else if (item is FileInfo fileInfo) - { - if (GroupFoldersAndFiles) - { - if (i == 0) - { - items.Add(new FileSystemEntriesGroupHeader() { Header = Localization.Strings.Files, ShowSeparator = false }); - } - else if (directoriesAndFiles[i - 1] is DirectoryInfo) - { - items.Add(new FileSystemEntriesGroupHeader() { Header = Localization.Strings.Files, ShowSeparator = true }); - } - } - - bool isSelected = fileInfo.FullName == m_controller.CurrentFileFullName; - - items.Add(new FileInfoItem() { IsSelected = isSelected, Value = fileInfo }); - } - } - - return items; + return FileControlHelper.GetFileSystemEntryItems( + directoriesAndFiles, + m_controller, + GroupFoldersAndFiles, + fileInfo => fileInfo.FullName == m_controller.CurrentFileFullName + ); } protected void UpdateFileFiltersVisibility() { - if (m_fileFiltersComboBox != null - && m_fileFiltersComboBox.ItemsSource != null - && m_fileFiltersComboBox.ItemsSource.GetEnumerator().MoveNext()) - { - m_fileFiltersComboBox.Visibility = Visibility.Visible; - } - else - { - m_fileFiltersComboBox.Visibility = Visibility.Collapsed; - } + FileControlHelper.UpdateFileFiltersVisibility(m_fileFiltersComboBox); } } @@ -426,7 +416,7 @@ protected void UpdateFileFiltersVisibility() public class FileSelectedEventArgs : RoutedEventArgs { /// - /// The selected file as + /// The selected file as . /// public FileInfo FileInfo { get; private set; } diff --git a/MaterialDesignExtensions/Controls/BaseFileDialog.cs b/MaterialDesignExtensions/Controls/BaseFileDialog.cs index 03ad50c3..21f91f7f 100644 --- a/MaterialDesignExtensions/Controls/BaseFileDialog.cs +++ b/MaterialDesignExtensions/Controls/BaseFileDialog.cs @@ -7,11 +7,14 @@ using System.Threading.Tasks; using System.Windows; -using MaterialDesignThemes.Wpf; - using MaterialDesignExtensions.Converters; using MaterialDesignExtensions.Model; +// use Pri.LongPath classes instead of System.IO for the MaterialDesignExtensions.LongPath build to support long file system paths on older Windows and .NET versions +#if LONG_PATH +using FileInfo = Pri.LongPath.FileInfo; +#endif + namespace MaterialDesignExtensions.Controls { /// @@ -176,6 +179,17 @@ public FileDialogArguments() Filters = null; FilterIndex = 0; } + + /// + /// Copy constructor + /// + /// + public FileDialogArguments(FileDialogArguments args) + : base(args) + { + Filters = args.Filters; + FilterIndex = args.FilterIndex; + } } /// diff --git a/MaterialDesignExtensions/Controls/BusyOverlay.cs b/MaterialDesignExtensions/Controls/BusyOverlay.cs new file mode 100644 index 00000000..955d9402 --- /dev/null +++ b/MaterialDesignExtensions/Controls/BusyOverlay.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; + +namespace MaterialDesignExtensions.Controls +{ + /// + /// A control for displaying some kind of progress indication over the complete user interface while a long running operation is in progress. + /// + public class BusyOverlay : ContentControl + { + /// + /// True, to switch the control into busy state and make it visible in the UI's foreground. + /// + public static readonly DependencyProperty IsBusyProperty = DependencyProperty.Register( + nameof(IsBusy), typeof(bool), typeof(BusyOverlay), new PropertyMetadata(false)); + + /// + /// True, to switch the control into busy state and make it visible in the UI's foreground. + /// + public bool IsBusy + { + get + { + return (bool)GetValue(IsBusyProperty); + } + + set + { + SetValue(IsBusyProperty, value); + } + } + + /// + /// The progress in percentage of the operation causing the busy state. + /// + public static readonly DependencyProperty ProgressProperty = DependencyProperty.Register( + nameof(Progress), typeof(int), typeof(BusyOverlay), new PropertyMetadata(0)); + + /// + /// The progress in percentage of the operation causing the busy state. + /// + public int Progress + { + get + { + return (int)GetValue(ProgressProperty); + } + + set + { + SetValue(ProgressProperty, value); + } + } + + /// + /// Creates a new . + /// + public BusyOverlay() : base() { } + } +} diff --git a/MaterialDesignExtensions/Controls/ConfirmationDialog.cs b/MaterialDesignExtensions/Controls/ConfirmationDialog.cs new file mode 100644 index 00000000..796557c6 --- /dev/null +++ b/MaterialDesignExtensions/Controls/ConfirmationDialog.cs @@ -0,0 +1,196 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; + +using MaterialDesignThemes.Wpf; + +namespace MaterialDesignExtensions.Controls +{ + /// + /// A confirmation dialog to show a title and message with an OK and a cancel button. + /// + public class ConfirmationDialog : MessageDialog + { + private static readonly string CancelButtonName = "cancelButton"; + + /// + /// The label of the cancel button. + /// + public static readonly DependencyProperty CancelButtonLabelProperty = DependencyProperty.Register( + nameof(CancelButtonLabel), typeof(string), typeof(ConfirmationDialog)); + + /// + /// The label of the cancel button. + /// + public string CancelButtonLabel + { + get + { + return (string)GetValue(CancelButtonLabelProperty); + } + + set + { + SetValue(CancelButtonLabelProperty, value); + } + } + + /// + /// True to stack the OK and cancel buttons instead of showing them side by side. + /// + public static readonly DependencyProperty StackedButtonsProperty = DependencyProperty.Register( + nameof(StackedButtons), typeof(bool), typeof(ConfirmationDialog)); + + /// + /// True to stack the OK and cancel buttons instead of showing them side by side. + /// + public bool StackedButtons + { + get + { + return (bool)GetValue(StackedButtonsProperty); + } + + set + { + SetValue(StackedButtonsProperty, value); + } + } + + private Button m_cancelButton; + + static ConfirmationDialog() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(ConfirmationDialog), new FrameworkPropertyMetadata(typeof(ConfirmationDialog))); + } + + /// + /// Creates a new . + /// + public ConfirmationDialog() + : base() + { + m_cancelButton = null; + } + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + if (m_cancelButton != null) + { + m_cancelButton.Click -= CancelButtonClickHandler; + } + + m_cancelButton = Template.FindName(CancelButtonName, this) as Button; + } + + protected override void LoadedHandler(object sender, RoutedEventArgs args) + { + base.LoadedHandler(sender, args); + + m_cancelButton.Click += CancelButtonClickHandler; + } + + protected override void UnloadedHandler(object sender, RoutedEventArgs args) + { + base.UnloadedHandler(sender, args); + + m_cancelButton.Click -= CancelButtonClickHandler; + } + + protected override void OkButtonClickHandler(object sender, RoutedEventArgs args) + { + DialogHost.CloseDialogCommand.Execute(true, GetDialogHost()); + } + + private void CancelButtonClickHandler(object sender, RoutedEventArgs args) + { + DialogHost.CloseDialogCommand.Execute(false, GetDialogHost()); + } + + /// + /// Shows a new . + /// + /// The name of the + /// The arguments for the dialog initialization + /// + public static async Task ShowDialogAsync(string dialogHostName, ConfirmationDialogArguments args) + { + ConfirmationDialog dialog = InitDialog(args); + + object result = await DialogHost.Show(dialog, dialogHostName, args.OpenedHandler, args.ClosingHandler); + + return (bool)result; + } + + /// + /// Shows a new . + /// + /// The + /// The arguments for the dialog initialization + /// + public static async Task ShowDialogAsync(DialogHost dialogHost, ConfirmationDialogArguments args) + { + ConfirmationDialog dialog = InitDialog(args); + + object result = await dialogHost.ShowDialog(dialog, args.OpenedHandler, args.ClosingHandler); + + return (bool)result; + } + + private static ConfirmationDialog InitDialog(ConfirmationDialogArguments args) + { + ConfirmationDialog dialog = new ConfirmationDialog + { + Title = args.Title, + Message = args.Message, + StackedButtons = args.StackedButtons, + CustomContent = args.CustomContent, + CustomContentTemplate = args.CustomContentTemplate + }; + + if (!string.IsNullOrWhiteSpace(args.OkButtonLabel)) + { + dialog.OkButtonLabel = args.OkButtonLabel; + } + + if (!string.IsNullOrWhiteSpace(args.CancelButtonLabel)) + { + dialog.CancelButtonLabel = args.CancelButtonLabel; + } + + return dialog; + } + } + + /// + /// The arguments for a confirmation dialog. + /// + public class ConfirmationDialogArguments : MessageDialogArguments + { + /// + /// The label of the cancel button. + /// + public string CancelButtonLabel { get; set; } + + /// + /// True to stack the OK and cancel buttons instead of showing them side by side. + /// + public bool StackedButtons { get; set; } + + /// + /// Creates a new . + /// + public ConfirmationDialogArguments() + : base() + { + CancelButtonLabel = null; + StackedButtons = false; + } + } +} diff --git a/MaterialDesignExtensions/Controls/ControlWithAutocompletePopup.cs b/MaterialDesignExtensions/Controls/ControlWithAutocompletePopup.cs index 5c9e3992..e858c9d6 100644 --- a/MaterialDesignExtensions/Controls/ControlWithAutocompletePopup.cs +++ b/MaterialDesignExtensions/Controls/ControlWithAutocompletePopup.cs @@ -19,7 +19,7 @@ public abstract class ControlWithAutocompletePopup : Control protected AutocompletePopup m_popup; /// - /// Creates a new ControlWithPopup. + /// Creates a new . /// public ControlWithAutocompletePopup() : base() diff --git a/MaterialDesignExtensions/Controls/FileSystemControl.cs b/MaterialDesignExtensions/Controls/FileSystemControl.cs index 3a9d19ce..f8e6c8e0 100644 --- a/MaterialDesignExtensions/Controls/FileSystemControl.cs +++ b/MaterialDesignExtensions/Controls/FileSystemControl.cs @@ -13,9 +13,16 @@ using MaterialDesignThemes.Wpf; +using MaterialDesignExtensions.Commands.Internal; using MaterialDesignExtensions.Controllers; using MaterialDesignExtensions.Model; +// use Pri.LongPath classes instead of System.IO for the MaterialDesignExtensions.LongPath build to support long file system paths on older Windows and .NET versions +#if LONG_PATH +using FileSystemInfo = Pri.LongPath.FileSystemInfo; +using DirectoryInfo = Pri.LongPath.DirectoryInfo; +#endif + namespace MaterialDesignExtensions.Controls { /// @@ -26,34 +33,10 @@ public abstract class FileSystemControl : Control protected const string DrawerHostName = "drawerHost"; protected const string PathPartsScrollViewerName = "pathPartsScrollViewer"; protected const string PathPartsItemsControlName = "pathPartsItemsControl"; + protected const string CurrentDirectoryTextBoxName = "currentDirectoryTextBox"; protected const string FileSystemEntryItemsControlName = "fileSystemEntryItemsControl"; protected const string EmptyDirectoryTextBlockName = "emptyDirectoryTextBlock"; - /// - /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. - /// - public static readonly RoutedCommand OpenSpecialDirectoriesDrawerCommand = new RoutedCommand(); - - /// - /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. - /// - public static readonly RoutedCommand SelectDirectoryItemCommand = new RoutedCommand(); - - /// - /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. - /// - public static readonly RoutedCommand SelectFileSystemEntryCommand = new RoutedCommand(); - - /// - /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. - /// - public static readonly RoutedCommand ShowInfoCommand = new RoutedCommand(); - - /// - /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. - /// - public static readonly RoutedCommand CancelCommand = new RoutedCommand(); - /// /// An event raised by canceling the operation. /// @@ -87,6 +70,31 @@ public FileSystemController Controller } } + /// + /// Enables the feature to create new directories. + /// + public static readonly DependencyProperty CreateNewDirectoryEnabledProperty = DependencyProperty.Register( + nameof(CreateNewDirectoryEnabled), + typeof(bool), + typeof(FileSystemControl), + new PropertyMetadata(false)); + + /// + /// Enables the feature to create new directories. Notice: It does not have any effects for . + /// + public bool CreateNewDirectoryEnabled + { + get + { + return (bool)GetValue(CreateNewDirectoryEnabledProperty); + } + + set + { + SetValue(CreateNewDirectoryEnabledProperty, value); + } + } + /// /// The current directory of the control. /// @@ -170,6 +178,56 @@ ScrollViewer FindItemsScrollViewer(ItemsControl itemsControl) } } + /// + /// The name for the new directory. + /// + public static readonly DependencyProperty NewDirectoryNameProperty = DependencyProperty.Register( + nameof(NewDirectoryName), + typeof(string), + typeof(FileSystemControl), + new PropertyMetadata(null)); + + /// + /// The name for the new directory. + /// + public string NewDirectoryName + { + get + { + return (string)GetValue(NewDirectoryNameProperty); + } + + set + { + SetValue(NewDirectoryNameProperty, value); + } + } + + /// + /// Show the current directory path with a short cut button for each part in the path, instead of a text box. + /// + public static readonly DependencyProperty PathPartsAsButtonsProperty = DependencyProperty.Register( + nameof(PathPartsAsButtons), + typeof(bool), + typeof(FileSystemControl), + new PropertyMetadata(true)); + + /// + /// Show the current directory path with a short cut button for each part in the path, instead of a text box. + /// + public bool PathPartsAsButtons + { + get + { + return (bool)GetValue(PathPartsAsButtonsProperty); + } + + set + { + SetValue(PathPartsAsButtonsProperty, value); + } + } + /// /// Shows or hides hidden directories and files. /// @@ -245,12 +303,38 @@ protected set } } + /// + /// Enable switching between a text box and the buttons for each sub directory of the current directory's path. + /// + public static readonly DependencyProperty SwitchPathPartsAsButtonsEnabledProperty = DependencyProperty.Register( + nameof(SwitchPathPartsAsButtonsEnabled), + typeof(bool), + typeof(FileSystemControl), + new PropertyMetadata(false)); + + /// + /// Enable switching between a text box and the buttons for each sub directory of the current directory's path. + /// + public bool SwitchPathPartsAsButtonsEnabled + { + get + { + return (bool)GetValue(SwitchPathPartsAsButtonsEnabledProperty); + } + + set + { + SetValue(SwitchPathPartsAsButtonsEnabledProperty, value); + } + } + protected FileSystemController m_controller; protected ScrollViewer m_pathPartsScrollViewer; protected ItemsControl m_pathPartsItemsControl; + protected TextBox m_currentDirectoryTextBox; // use an ItemsControl instead of a ListBox, because the ListBox raises several selection changed events without an explicit user input - // another advantage; non selectable items such as headers can be added + // another advantage: non selectable items such as headers can be added protected ItemsControl m_fileSystemEntryItemsControl; // private to force the usage of the lazy getter, because it only works after applying the template private ScrollViewer m_fileSystemEntryItemsScrollViewer; @@ -261,20 +345,30 @@ static FileSystemControl() DefaultStyleKeyProperty.OverrideMetadata(typeof(FileSystemControl), new FrameworkPropertyMetadata(typeof(FileSystemControl))); } + /// + /// Creates a new . + /// public FileSystemControl() : base() { m_controller = new FileSystemController(); m_controller.SelectDirectory(CurrentDirectory); - CommandBindings.Add(new CommandBinding(OpenSpecialDirectoriesDrawerCommand, OpenSpecialDirectoriesDrawerCommandHandler)); - CommandBindings.Add(new CommandBinding(SelectDirectoryItemCommand, SelectDirectoryItemCommandHandler)); - CommandBindings.Add(new CommandBinding(SelectFileSystemEntryCommand, SelectFileSystemEntryCommandHandler)); - CommandBindings.Add(new CommandBinding(ShowInfoCommand, ShowInfoCommandHandler)); - CommandBindings.Add(new CommandBinding(CancelCommand, CancelCommandHandler)); + CommandBindings.Add(new CommandBinding(FileSystemControlCommands.OpenSpecialDirectoriesDrawerCommand, OpenSpecialDirectoriesDrawerCommandHandler)); + CommandBindings.Add(new CommandBinding(FileSystemControlCommands.SwitchPathPartsAsButtonsCommand, SwitchPathPartsAsButtonsHandler)); + CommandBindings.Add(new CommandBinding(FileSystemControlCommands.SelectDirectoryItemCommand, SelectDirectoryItemCommandHandler)); + CommandBindings.Add(new CommandBinding(FileSystemControlCommands.SelectFileSystemEntryCommand, SelectFileSystemEntryCommandHandler)); + CommandBindings.Add(new CommandBinding(FileSystemControlCommands.ShowInfoCommand, ShowInfoCommandHandler)); + CommandBindings.Add(new CommandBinding(FileSystemControlCommands.CancelCommand, CancelCommandHandler)); + CommandBindings.Add(new CommandBinding(FileSystemControlCommands.ShowCreateNewDirectoryCommand, ShowCreateNewDirectoryCommandHandler)); + CommandBindings.Add(new CommandBinding(FileSystemControlCommands.CancelNewDirectoryCommand, CancelNewDirectoryCommandHandler)); + CommandBindings.Add(new CommandBinding(FileSystemControlCommands.CreateNewDirectoryCommand, CreateNewDirectoryCommandHandler)); + + InputBindings.Add(new KeyBinding(FileSystemControlCommands.CancelCommand, new KeyGesture(Key.Escape))); m_pathPartsScrollViewer = null; m_pathPartsItemsControl = null; + m_currentDirectoryTextBox = null; m_fileSystemEntryItemsScrollViewer = null; m_fileSystemEntryItemsControl = null; @@ -291,6 +385,9 @@ public override void OnApplyTemplate() m_pathPartsItemsControl = Template.FindName(PathPartsItemsControlName, this) as ItemsControl; m_pathPartsItemsControl.ItemsSource = m_controller.CurrentDirectoryPathParts; + m_currentDirectoryTextBox = Template.FindName(CurrentDirectoryTextBoxName, this) as TextBox; + m_currentDirectoryTextBox.Text = CurrentDirectory; + m_fileSystemEntryItemsControl = Template.FindName(FileSystemEntryItemsControlName, this) as ItemsControl; m_fileSystemEntryItemsControl.ItemsSource = GetFileSystemEntryItems(); @@ -302,11 +399,24 @@ public override void OnApplyTemplate() protected virtual void LoadedHandler(object sender, RoutedEventArgs args) { m_controller.PropertyChanged += ControllerPropertyChangedHandler; + + if (m_currentDirectoryTextBox != null) + { + m_currentDirectoryTextBox.KeyDown += CurrentDirectoryTextBoxKeyDownHandler; + } + + // not sure if the control should get keyboard focus on loading + //Keyboard.Focus(this); } protected virtual void UnloadedHandler(object sender, RoutedEventArgs args) { m_controller.PropertyChanged -= ControllerPropertyChangedHandler; + + if (m_currentDirectoryTextBox != null) + { + m_currentDirectoryTextBox.KeyDown -= CurrentDirectoryTextBoxKeyDownHandler; + } } protected void OpenSpecialDirectoriesDrawerCommandHandler(object sender, ExecutedRoutedEventArgs args) @@ -315,6 +425,28 @@ protected void OpenSpecialDirectoriesDrawerCommandHandler(object sender, Execute drawerHost.IsTopDrawerOpen = true; } + protected void SwitchPathPartsAsButtonsHandler(object sender, ExecutedRoutedEventArgs args) + { + PathPartsAsButtons = !PathPartsAsButtons; + } + + private void CurrentDirectoryTextBoxKeyDownHandler(object sender, KeyEventArgs args) + { + if (sender == m_currentDirectoryTextBox && args.Key == Key.Enter) + { + string directory = m_currentDirectoryTextBox.Text + .Replace("\n", string.Empty) + .Replace("\r", string.Empty) + .Replace("\r", string.Empty) + .Trim(); + + if (!string.IsNullOrWhiteSpace(directory)) + { + CurrentDirectory = directory; + } + } + } + protected void SelectDirectoryItemCommandHandler(object sender, ExecutedRoutedEventArgs args) { if (args.Parameter != null) @@ -339,6 +471,8 @@ protected void SelectDirectoryItemCommandHandler(object sender, ExecutedRoutedEv drawerHost.IsTopDrawerOpen = false; } } + + Keyboard.Focus(this); } protected virtual void SelectFileSystemEntryCommandHandler(object sender, ExecutedRoutedEventArgs args) @@ -350,6 +484,8 @@ protected virtual void SelectFileSystemEntryCommandHandler(object sender, Execut CurrentDirectory = directoryInfo.FullName; } } + + Keyboard.Focus(this); } protected void ShowInfoCommandHandler(object sender, ExecutedRoutedEventArgs args) @@ -366,6 +502,43 @@ protected void CancelCommandHandler(object sender, ExecutedRoutedEventArgs args) RaiseEvent(eventArgs); } + protected void ShowCreateNewDirectoryCommandHandler(object sender, ExecutedRoutedEventArgs args) + { + NewDirectoryName = null; + + DrawerHost drawerHost = ((DrawerHost)Template.FindName(DrawerHostName, this)); + drawerHost.IsBottomDrawerOpen = true; + } + + protected void CancelNewDirectoryCommandHandler(object sender, ExecutedRoutedEventArgs args) + { + DrawerHost drawerHost = ((DrawerHost)Template.FindName(DrawerHostName, this)); + + if (drawerHost.IsBottomDrawerOpen) + { + drawerHost.IsBottomDrawerOpen = false; + } + } + + protected void CreateNewDirectoryCommandHandler(object sender, ExecutedRoutedEventArgs args) + { + try + { + m_controller.CreateNewDirectory(NewDirectoryName); + + DrawerHost drawerHost = ((DrawerHost)Template.FindName(DrawerHostName, this)); + + if (drawerHost.IsBottomDrawerOpen) + { + drawerHost.IsBottomDrawerOpen = false; + } + } + catch (Exception exc) + { + SnackbarMessageQueue.Enqueue(exc.Message); + } + } + protected static void CurrentDirectoryChangedHandler(DependencyObject obj, DependencyPropertyChangedEventArgs args) { (obj as FileSystemControl)?.CurrentDirectoryChangedHandler(args.NewValue as string); @@ -376,6 +549,11 @@ protected virtual void CurrentDirectoryChangedHandler(string newCurrentDirectory try { m_controller.SelectDirectory(newCurrentDirectory); + + if (m_currentDirectoryTextBox != null) + { + m_currentDirectoryTextBox.Text = newCurrentDirectory; + } } catch (PathTooLongException) { @@ -425,7 +603,7 @@ protected virtual void ControllerPropertyChangedHandler(object sender, PropertyC } } - protected void UpdateSelection() + protected virtual void UpdateSelection() { IEnumerable items = m_fileSystemEntryItemsControl?.ItemsSource; diff --git a/MaterialDesignExtensions/Controls/FileSystemDialog.cs b/MaterialDesignExtensions/Controls/FileSystemDialog.cs index ddccc6bb..2dec4283 100644 --- a/MaterialDesignExtensions/Controls/FileSystemDialog.cs +++ b/MaterialDesignExtensions/Controls/FileSystemDialog.cs @@ -16,6 +16,31 @@ namespace MaterialDesignExtensions.Controls /// public abstract class FileSystemDialog : Control { + /// + /// Enables the feature to create new directories. + /// + public static readonly DependencyProperty CreateNewDirectoryEnabledProperty = DependencyProperty.Register( + nameof(CreateNewDirectoryEnabled), + typeof(bool), + typeof(FileSystemDialog), + new PropertyMetadata(false)); + + /// + /// Enables the feature to create new directories. Notice: It does not have any effects for . + /// + public bool CreateNewDirectoryEnabled + { + get + { + return (bool)GetValue(CreateNewDirectoryEnabledProperty); + } + + set + { + SetValue(CreateNewDirectoryEnabledProperty, value); + } + } + /// /// The current directory of the dialog. /// @@ -41,6 +66,31 @@ public string CurrentDirectory } } + /// + /// Show the current directory path with a short cut button for each part in the path, instead of a text box. + /// + public static readonly DependencyProperty PathPartsAsButtonsProperty = DependencyProperty.Register( + nameof(PathPartsAsButtons), + typeof(bool), + typeof(FileSystemDialog), + new PropertyMetadata(true)); + + /// + /// Show the current directory path with a short cut button for each part in the path, instead of a text box. + /// + public bool PathPartsAsButtons + { + get + { + return (bool)GetValue(PathPartsAsButtonsProperty); + } + + set + { + SetValue(PathPartsAsButtonsProperty, value); + } + } + /// /// Shows or hides hidden directories and files. /// @@ -91,6 +141,31 @@ public bool ShowSystemFilesAndDirectories } } + /// + /// Enable switching between a text box and the buttons for each sub directory of the current directory's path. + /// + public static readonly DependencyProperty SwitchPathPartsAsButtonsEnabledProperty = DependencyProperty.Register( + nameof(SwitchPathPartsAsButtonsEnabled), + typeof(bool), + typeof(FileSystemDialog), + new PropertyMetadata(false)); + + /// + /// Enable switching between a text box and the buttons for each sub directory of the current directory's path. + /// + public bool SwitchPathPartsAsButtonsEnabled + { + get + { + return (bool)GetValue(SwitchPathPartsAsButtonsEnabledProperty); + } + + set + { + SetValue(SwitchPathPartsAsButtonsEnabledProperty, value); + } + } + static FileSystemDialog() { DefaultStyleKeyProperty.OverrideMetadata(typeof(FileSystemDialog), new FrameworkPropertyMetadata(typeof(FileSystemDialog))); @@ -115,7 +190,9 @@ protected DialogHost GetDialogHost() protected static void InitDialog(FileSystemDialog dialog, double? width, double? height, string currentDirectory, - bool showHiddenFilesAndDirectories, bool showSystemFilesAndDirectories) + bool showHiddenFilesAndDirectories, bool showSystemFilesAndDirectories, + bool createNewDirectoryEnabled = false, + bool switchPathPartsAsButtonsEnabled = false, bool pathPartsAsButtons = true) { if (width != null) { @@ -132,8 +209,11 @@ protected static void InitDialog(FileSystemDialog dialog, double? width, double? dialog.CurrentDirectory = currentDirectory; } + dialog.CreateNewDirectoryEnabled = createNewDirectoryEnabled; dialog.ShowHiddenFilesAndDirectories = showHiddenFilesAndDirectories; dialog.ShowSystemFilesAndDirectories = showSystemFilesAndDirectories; + dialog.SwitchPathPartsAsButtonsEnabled = switchPathPartsAsButtonsEnabled; + dialog.PathPartsAsButtons = pathPartsAsButtons; } } @@ -148,7 +228,7 @@ public abstract class FileSystemDialogArguments public double? Width { get; set; } /// - /// The fixed heigth of the dialog (nullable). + /// The fixed height of the dialog (nullable). /// public double? Height { get; set; } @@ -157,6 +237,16 @@ public abstract class FileSystemDialogArguments /// public string CurrentDirectory { get; set; } + /// + /// Enables the feature to create new directories. Notice: It does not have any effects for . + /// + public bool CreateNewDirectoryEnabled { get; set; } + + /// + /// Show the current directory path with a short cut button for each part in the path, instead of a text box. + /// + public bool PathPartsAsButtons { get; set; } + /// /// Shows or hides hidden directories and files. /// @@ -167,6 +257,11 @@ public abstract class FileSystemDialogArguments /// public bool ShowSystemFilesAndDirectories { get; set; } + /// + /// Enable switching between a text box and the buttons for each sub directory of the current directory's path. + /// + public bool SwitchPathPartsAsButtonsEnabled { get; set; } + /// /// Callback after openening the dialog. /// @@ -185,11 +280,30 @@ public FileSystemDialogArguments() Width = null; Height = null; CurrentDirectory = null; + CreateNewDirectoryEnabled = false; ShowHiddenFilesAndDirectories = false; ShowSystemFilesAndDirectories = false; + SwitchPathPartsAsButtonsEnabled = false; + PathPartsAsButtons = true; OpenedHandler = null; ClosingHandler = null; } + + /// + /// Copy constructor + /// + /// + public FileSystemDialogArguments(FileSystemDialogArguments args) + { + Width = args.Width; + Height = args.Height; + CurrentDirectory = args.CurrentDirectory; + CreateNewDirectoryEnabled = args.CreateNewDirectoryEnabled; + ShowHiddenFilesAndDirectories = args.ShowHiddenFilesAndDirectories; + ShowSystemFilesAndDirectories = args.ShowSystemFilesAndDirectories; + OpenedHandler = args.OpenedHandler; + ClosingHandler = args.ClosingHandler; + } } /// @@ -202,6 +316,17 @@ public abstract class FileSystemDialogResult /// public bool Canceled { get; protected set; } + /// + /// true, if the dialog was confirmed + /// + public bool Confirmed + { + get + { + return !Canceled; + } + } + /// /// Creates a new . /// diff --git a/MaterialDesignExtensions/Controls/IStepper.cs b/MaterialDesignExtensions/Controls/IStepper.cs index 0f963b00..f23bf6c0 100644 --- a/MaterialDesignExtensions/Controls/IStepper.cs +++ b/MaterialDesignExtensions/Controls/IStepper.cs @@ -6,6 +6,7 @@ using System.Windows; using System.Windows.Input; +using MaterialDesignExtensions.Controllers; using MaterialDesignExtensions.Model; namespace MaterialDesignExtensions.Controls @@ -56,6 +57,16 @@ public interface IStepper /// ICommand ContinueNavigationCommand { get; set; } + /// + /// Gets the controller for this . + /// + StepperController Controller { get; } + + /// + /// An alternative icon template done steps. + /// + DataTemplate DoneIconTemplate { get; set; } + /// /// Enables the linear mode by disabling the buttons of the header. /// The navigation must be accomplished by using the navigation commands. @@ -71,5 +82,10 @@ public interface IStepper /// A command called by navigating to an arbitrary in a non-linear stepper. /// ICommand StepNavigationCommand { get; set; } + + /// + /// An alternative icon template to indicate validation errors. + /// + DataTemplate ValidationErrorIconTemplate { get; set; } } } diff --git a/MaterialDesignExtensions/Controls/InputDialog.cs b/MaterialDesignExtensions/Controls/InputDialog.cs new file mode 100644 index 00000000..4b2b34df --- /dev/null +++ b/MaterialDesignExtensions/Controls/InputDialog.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; + +using MaterialDesignThemes.Wpf; + +namespace MaterialDesignExtensions.Controls +{ + public class InputDialog : ConfirmationDialog + { + /// + /// A handler called for validation right before confirming the dialog. + /// + public static readonly DependencyProperty ValidationHandlerProperty = DependencyProperty.Register( + nameof(ValidationHandler), typeof(InputDialogValidationEventHandler), typeof(InputDialog)); + + /// + /// A handler called for validation right before confirming the dialog. + /// + public InputDialogValidationEventHandler ValidationHandler + { + get + { + return (InputDialogValidationEventHandler)GetValue(ValidationHandlerProperty); + } + + set + { + SetValue(ValidationHandlerProperty, value); + } + } + + static InputDialog() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(InputDialog), new FrameworkPropertyMetadata(typeof(InputDialog))); + } + + /// + /// Creates a new . + /// + public InputDialog() : base() { } + + protected override void OkButtonClickHandler(object sender, RoutedEventArgs args) + { + InputDialogValidationEventArgs validationArgs = new InputDialogValidationEventArgs { CancelConfirmation = false }; + + ValidationHandler?.Invoke(this, validationArgs); + + if (!validationArgs.CancelConfirmation) + { + DialogHost.CloseDialogCommand.Execute(true, GetDialogHost()); + } + } + + /// + /// Shows a new . + /// + /// The name of the + /// The arguments for the dialog initialization + /// + public static async Task ShowDialogAsync(string dialogHostName, InputDialogArguments args) + { + InputDialog dialog = InitDialog(args); + + object result = await DialogHost.Show(dialog, dialogHostName, args.OpenedHandler, args.ClosingHandler); + + return (bool)result; + } + + /// + /// Shows a new . + /// + /// The + /// The arguments for the dialog initialization + /// + public static async Task ShowDialogAsync(DialogHost dialogHost, InputDialogArguments args) + { + InputDialog dialog = InitDialog(args); + + object result = await dialogHost.ShowDialog(dialog, args.OpenedHandler, args.ClosingHandler); + + return (bool)result; + } + + private static InputDialog InitDialog(InputDialogArguments args) + { + InputDialog dialog = new InputDialog + { + Title = args.Title, + Message = args.Message, + StackedButtons = args.StackedButtons, + CustomContent = args.CustomContent, + CustomContentTemplate = args.CustomContentTemplate, + ValidationHandler = args.ValidationHandler + }; + + if (!string.IsNullOrWhiteSpace(args.OkButtonLabel)) + { + dialog.OkButtonLabel = args.OkButtonLabel; + } + + if (!string.IsNullOrWhiteSpace(args.CancelButtonLabel)) + { + dialog.CancelButtonLabel = args.CancelButtonLabel; + } + + return dialog; + } + } + + /// + /// The arguments for a confirmation dialog. + /// + public class InputDialogArguments : ConfirmationDialogArguments + { + /// + /// A handler called for validation right before confirming the dialog. + /// + public InputDialogValidationEventHandler ValidationHandler { get; set; } + + /// + /// Creates a new . + /// + public InputDialogArguments() + : base() + { + ValidationHandler = null; + } + } + + /// + /// The arguments for the validation handler. + /// + public class InputDialogValidationEventArgs + { + public bool CancelConfirmation { get; set; } + + /// + /// Creates a new . + /// + public InputDialogValidationEventArgs() + { + CancelConfirmation = true; + } + } + + /// + /// The delegate for handling the input dialog validation. + /// + /// + /// + public delegate void InputDialogValidationEventHandler(object sender, InputDialogValidationEventArgs args); +} diff --git a/MaterialDesignExtensions/Controls/MaterialNavigationWindow.cs b/MaterialDesignExtensions/Controls/MaterialNavigationWindow.cs new file mode 100644 index 00000000..f4e5c1c3 --- /dev/null +++ b/MaterialDesignExtensions/Controls/MaterialNavigationWindow.cs @@ -0,0 +1,204 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Navigation; + +namespace MaterialDesignExtensions.Controls +{ + /// + /// Custom navigation window class for a Material Design like styled window. + /// + public class MaterialNavigationWindow : NavigationWindow + { + private const string MinimizeButtonName = "minimizeButton"; + private const string MaximizeRestoreButtonName = "maximizeRestoreButton"; + private const string CloseButtonName = "closeButton"; + + /// + /// The color for the border and caption area background of the window. + /// + public static readonly DependencyProperty BorderBackgroundBrushProperty = DependencyProperty.Register( + nameof(BorderBackgroundBrush), typeof(Brush), typeof(MaterialNavigationWindow), new FrameworkPropertyMetadata(null, null)); + + /// + /// The color for the border and caption area background of the window. + /// + public Brush BorderBackgroundBrush + { + get + { + return (Brush)GetValue(BorderBackgroundBrushProperty); + } + + set + { + SetValue(BorderBackgroundBrushProperty, value); + } + } + + /// + /// The forground color for the caption area of the window. + /// + public static readonly DependencyProperty BorderForegroundBrushProperty = DependencyProperty.Register( + nameof(BorderForegroundBrush), typeof(Brush), typeof(MaterialNavigationWindow), new FrameworkPropertyMetadata(null, null)); + + /// + /// The forground color for the caption area of the window. + /// + public Brush BorderForegroundBrush + { + get + { + return (Brush)GetValue(BorderForegroundBrushProperty); + } + + set + { + SetValue(BorderForegroundBrushProperty, value); + } + } + + /// + /// Lets the content of the window fade out if the window is inactive. + /// The default is true (enabled). + /// + public static readonly DependencyProperty FadeContentIfInactiveProperty = DependencyProperty.Register( + nameof(FadeContentIfInactive), typeof(bool), typeof(MaterialNavigationWindow), new FrameworkPropertyMetadata(true)); + + /// + /// Lets the content of the window fade out if the window is inactive. + /// The default is true (enabled). + /// + public bool FadeContentIfInactive + { + get + { + return (bool)GetValue(FadeContentIfInactiveProperty); + } + + set + { + SetValue(FadeContentIfInactiveProperty, value); + } + } + + /// + /// The template for the title bar. The default shows a with the title. + /// + public static readonly DependencyProperty TitleTemplateProperty = DependencyProperty.Register( + nameof(TitleTemplate), typeof(DataTemplate), typeof(MaterialNavigationWindow)); + + /// + /// The template for the title bar. The default shows a with the title. + /// + public DataTemplate TitleTemplate + { + get + { + return (DataTemplate)GetValue(TitleTemplateProperty); + } + + set + { + SetValue(TitleTemplateProperty, value); + } + } + + /// + /// The icon inside the window's title bar. + /// + public static readonly DependencyProperty TitleBarIconProperty = DependencyProperty.Register( + nameof(TitleBarIcon), typeof(ImageSource), typeof(MaterialNavigationWindow), new FrameworkPropertyMetadata(null, null)); + + /// + /// The icon inside the window's title bar. + /// + public ImageSource TitleBarIcon + { + get + { + return (ImageSource)GetValue(TitleBarIconProperty); + } + + set + { + SetValue(TitleBarIconProperty, value); + } + } + + private Button m_minimizeButton; + private Button m_maximizeRestoreButton; + private Button m_closeButton; + + static MaterialNavigationWindow() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(MaterialNavigationWindow), new FrameworkPropertyMetadata(typeof(MaterialNavigationWindow))); + } + + /// + /// Creates a new . + /// + public MaterialNavigationWindow() : base() { } + + public override void OnApplyTemplate() + { + if (m_minimizeButton != null) + { + m_minimizeButton.Click -= MinimizeButtonClickHandler; + } + + m_minimizeButton = GetTemplateChild(MinimizeButtonName) as Button; + + if (m_minimizeButton != null) + { + m_minimizeButton.Click += MinimizeButtonClickHandler; + } + + if (m_maximizeRestoreButton != null) + { + m_maximizeRestoreButton.Click -= MaximizeRestoreButtonClickHandler; + } + + m_maximizeRestoreButton = GetTemplateChild(MaximizeRestoreButtonName) as Button; + + if (m_maximizeRestoreButton != null) + { + m_maximizeRestoreButton.Click += MaximizeRestoreButtonClickHandler; + } + + if (m_closeButton != null) + { + m_closeButton.Click -= CloseButtonClickHandler; + } + + m_closeButton = GetTemplateChild(CloseButtonName) as Button; + + if (m_closeButton != null) + { + m_closeButton.Click += CloseButtonClickHandler; + } + + base.OnApplyTemplate(); + } + + private void CloseButtonClickHandler(object sender, RoutedEventArgs args) + { + Close(); + } + + private void MaximizeRestoreButtonClickHandler(object sender, RoutedEventArgs args) + { + WindowState = (WindowState == WindowState.Normal) ? WindowState.Maximized : WindowState.Normal; + } + + private void MinimizeButtonClickHandler(object sender, RoutedEventArgs args) + { + WindowState = WindowState.Minimized; + } + } +} diff --git a/MaterialDesignExtensions/Controls/MaterialWindow.cs b/MaterialDesignExtensions/Controls/MaterialWindow.cs new file mode 100644 index 00000000..bafb2b1d --- /dev/null +++ b/MaterialDesignExtensions/Controls/MaterialWindow.cs @@ -0,0 +1,203 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; + +namespace MaterialDesignExtensions.Controls +{ + /// + /// Custom window class for a Material Design like styled window. + /// + public class MaterialWindow : Window + { + private const string MinimizeButtonName = "minimizeButton"; + private const string MaximizeRestoreButtonName = "maximizeRestoreButton"; + private const string CloseButtonName = "closeButton"; + + /// + /// The color for the border and caption area background of the window. + /// + public static readonly DependencyProperty BorderBackgroundBrushProperty = DependencyProperty.Register( + nameof(BorderBackgroundBrush), typeof(Brush), typeof(MaterialWindow), new FrameworkPropertyMetadata(null, null)); + + /// + /// The color for the border and caption area background of the window. + /// + public Brush BorderBackgroundBrush + { + get + { + return (Brush)GetValue(BorderBackgroundBrushProperty); + } + + set + { + SetValue(BorderBackgroundBrushProperty, value); + } + } + + /// + /// The forground color for the caption area of the window. + /// + public static readonly DependencyProperty BorderForegroundBrushProperty = DependencyProperty.Register( + nameof(BorderForegroundBrush), typeof(Brush), typeof(MaterialWindow), new FrameworkPropertyMetadata(null, null)); + + /// + /// The forground color for the caption area of the window. + /// + public Brush BorderForegroundBrush + { + get + { + return (Brush)GetValue(BorderForegroundBrushProperty); + } + + set + { + SetValue(BorderForegroundBrushProperty, value); + } + } + + /// + /// Lets the content of the window fade out if the window is inactive. + /// The default is true (enabled). + /// + public static readonly DependencyProperty FadeContentIfInactiveProperty = DependencyProperty.Register( + nameof(FadeContentIfInactive), typeof(bool), typeof(MaterialWindow), new FrameworkPropertyMetadata(true)); + + /// + /// Lets the content of the window fade out if the window is inactive. + /// The default is true (enabled). + /// + public bool FadeContentIfInactive + { + get + { + return (bool)GetValue(FadeContentIfInactiveProperty); + } + + set + { + SetValue(FadeContentIfInactiveProperty, value); + } + } + + /// + /// The template for the title bar. The default shows a with the title. + /// + public static readonly DependencyProperty TitleTemplateProperty = DependencyProperty.Register( + nameof(TitleTemplate), typeof(DataTemplate), typeof(MaterialWindow)); + + /// + /// The template for the title bar. The default shows a with the title. + /// + public DataTemplate TitleTemplate + { + get + { + return (DataTemplate)GetValue(TitleTemplateProperty); + } + + set + { + SetValue(TitleTemplateProperty, value); + } + } + + /// + /// The icon inside the window's title bar. + /// + public static readonly DependencyProperty TitleBarIconProperty = DependencyProperty.Register( + nameof(TitleBarIcon), typeof(ImageSource), typeof(MaterialWindow), new FrameworkPropertyMetadata(null, null)); + + /// + /// The icon inside the window's title bar. + /// + public ImageSource TitleBarIcon + { + get + { + return (ImageSource)GetValue(TitleBarIconProperty); + } + + set + { + SetValue(TitleBarIconProperty, value); + } + } + + private Button m_minimizeButton; + private Button m_maximizeRestoreButton; + private Button m_closeButton; + + static MaterialWindow() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(MaterialWindow), new FrameworkPropertyMetadata(typeof(MaterialWindow))); + } + + /// + /// Creates a new . + /// + public MaterialWindow() : base() { } + + public override void OnApplyTemplate() + { + if (m_minimizeButton != null) + { + m_minimizeButton.Click -= MinimizeButtonClickHandler; + } + + m_minimizeButton = GetTemplateChild(MinimizeButtonName) as Button; + + if (m_minimizeButton != null) + { + m_minimizeButton.Click += MinimizeButtonClickHandler; + } + + if (m_maximizeRestoreButton != null) + { + m_maximizeRestoreButton.Click -= MaximizeRestoreButtonClickHandler; + } + + m_maximizeRestoreButton = GetTemplateChild(MaximizeRestoreButtonName) as Button; + + if (m_maximizeRestoreButton != null) + { + m_maximizeRestoreButton.Click += MaximizeRestoreButtonClickHandler; + } + + if (m_closeButton != null) + { + m_closeButton.Click -= CloseButtonClickHandler; + } + + m_closeButton = GetTemplateChild(CloseButtonName) as Button; + + if (m_closeButton != null) + { + m_closeButton.Click += CloseButtonClickHandler; + } + + base.OnApplyTemplate(); + } + + private void CloseButtonClickHandler(object sender, RoutedEventArgs args) + { + Close(); + } + + private void MaximizeRestoreButtonClickHandler(object sender, RoutedEventArgs args) + { + WindowState = (WindowState == WindowState.Normal) ? WindowState.Maximized : WindowState.Normal; + } + + private void MinimizeButtonClickHandler(object sender, RoutedEventArgs args) + { + WindowState = WindowState.Minimized; + } + } +} diff --git a/MaterialDesignExtensions/Controls/MessageDialog.cs b/MaterialDesignExtensions/Controls/MessageDialog.cs new file mode 100644 index 00000000..5056c3c5 --- /dev/null +++ b/MaterialDesignExtensions/Controls/MessageDialog.cs @@ -0,0 +1,241 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Media; +using System.Windows.Controls; + +using MaterialDesignThemes.Wpf; + +namespace MaterialDesignExtensions.Controls +{ + /// + /// The base class for simple dialogs with a title and a message. + /// + public abstract class MessageDialog : Control + { + private static readonly string OkButtonName = "okButton"; + + /// + /// An optional custom content to show in the dialog. + /// + public static readonly DependencyProperty CustomContentProperty = DependencyProperty.Register( + nameof(CustomContent), typeof(object), typeof(MessageDialog)); + + /// + /// An optional custom content to show in the dialog. + /// + public object CustomContent + { + get + { + return GetValue(CustomContentProperty); + } + + set + { + SetValue(CustomContentProperty, value); + } + } + + /// + /// An optional for . + /// + public static readonly DependencyProperty CustomContentTemplateProperty = DependencyProperty.Register( + nameof(CustomContentTemplate), typeof(DataTemplate), typeof(MessageDialog)); + + /// + /// An optional for . + /// + public DataTemplate CustomContentTemplate + { + get + { + return (DataTemplate)GetValue(CustomContentTemplateProperty); + } + + set + { + SetValue(CustomContentTemplateProperty, value); + } + } + + /// + /// The message to display inside the dialog. + /// + public static readonly DependencyProperty MessageProperty = DependencyProperty.Register( + nameof(Message), typeof(string), typeof(MessageDialog)); + + /// + /// The message to display inside the dialog. + /// + public string Message + { + get + { + return (string)GetValue(MessageProperty); + } + + set + { + SetValue(MessageProperty, value); + } + } + + /// + /// The label of the OK button. + /// + public static readonly DependencyProperty OkButtonLabelProperty = DependencyProperty.Register( + nameof(OkButtonLabel), typeof(string), typeof(MessageDialog)); + + /// + /// The label of the OK button. + /// + public string OkButtonLabel + { + get + { + return (string)GetValue(OkButtonLabelProperty); + } + + set + { + SetValue(OkButtonLabelProperty, value); + } + } + + /// + /// The title to display inside the dialog. + /// + public static readonly DependencyProperty TitleProperty = DependencyProperty.Register( + nameof(Title), typeof(string), typeof(MessageDialog)); + + /// + /// The title to display inside the dialog. + /// + public string Title + { + get + { + return (string)GetValue(TitleProperty); + } + + set + { + SetValue(TitleProperty, value); + } + } + + private Button m_okButton; + + static MessageDialog() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(MessageDialog), new FrameworkPropertyMetadata(typeof(MessageDialog))); + } + + /// + /// Creates a new . + /// + public MessageDialog() + : base() + { + m_okButton = null; + + Loaded += LoadedHandler; + Unloaded += UnloadedHandler; + } + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + if (m_okButton != null) + { + m_okButton.Click -= OkButtonClickHandler; + } + + m_okButton = Template.FindName(OkButtonName, this) as Button; + } + + protected virtual void LoadedHandler(object sender, RoutedEventArgs args) + { + m_okButton.Click += OkButtonClickHandler; + } + + protected virtual void UnloadedHandler(object sender, RoutedEventArgs args) + { + m_okButton.Click -= OkButtonClickHandler; + } + + protected virtual void OkButtonClickHandler(object sender, RoutedEventArgs args) + { + DialogHost.CloseDialogCommand.Execute(null, GetDialogHost()); + } + + protected DialogHost GetDialogHost() + { + DependencyObject element = VisualTreeHelper.GetParent(this); + + while (element != null && !(element is DialogHost)) + { + element = VisualTreeHelper.GetParent(element); + } + + return element as DialogHost; + } + } + + /// + /// The base class for arguments of simple dialogs with a title and a message. + /// + public class MessageDialogArguments + { + /// + /// An optional custom content to show in the dialog. + /// + public object CustomContent { get; set; } + + /// + /// An optional for . + /// + public DataTemplate CustomContentTemplate { get; set; } + + /// + /// The message to display inside the dialog. + /// + public string Message { get; set; } + + /// + /// The label of the OC button. + /// + public string OkButtonLabel { get; set; } + + /// + /// The title to display inside the dialog. + /// + public string Title { get; set; } + + /// + /// Callback after openening the dialog. + /// + public DialogOpenedEventHandler OpenedHandler { get; set; } + + /// + /// Callback after closing the dialog. + /// + public DialogClosingEventHandler ClosingHandler { get; set; } + + /// + /// Creates a new . + /// + public MessageDialogArguments() + { + Message = null; + Title = null; + OpenedHandler = null; + ClosingHandler = null; + } + } +} diff --git a/MaterialDesignExtensions/Controls/NavigationRail.cs b/MaterialDesignExtensions/Controls/NavigationRail.cs new file mode 100644 index 00000000..63375a1d --- /dev/null +++ b/MaterialDesignExtensions/Controls/NavigationRail.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using MaterialDesignExtensions.Model; + +namespace MaterialDesignExtensions.Controls +{ + /// + /// A special version of the side navigation. + /// + public class NavigationRail : SideNavigation + { + /// + /// Creates a new . + /// + public NavigationRail() : base() { } + + protected override void InitItems(IList values) + { + if (m_navigationItemsControl != null) + { + IList navigationItems = new List(); + + if (values != null) + { + foreach (object item in values) + { + if (item is FirstLevelNavigationItem navigationItem) + { + navigationItems.Add(navigationItem); + } + else + { + throw new NotSupportedException($"Not supported item type {item.GetType().FullName}"); + } + } + } + + m_navigationItemsControl.ItemsSource = navigationItems; + + INavigationItem selectedItem = navigationItems.FirstOrDefault(item => item.IsSelected); + + if (selectedItem != null) + { + if (SelectedItem != selectedItem) + { + SelectedItem = selectedItem; + } + } + else + { + SelectedItem = null; + } + } + } + } +} diff --git a/MaterialDesignExtensions/Controls/OpenDirectoryControl.cs b/MaterialDesignExtensions/Controls/OpenDirectoryControl.cs index 20cf0ef0..3587db48 100644 --- a/MaterialDesignExtensions/Controls/OpenDirectoryControl.cs +++ b/MaterialDesignExtensions/Controls/OpenDirectoryControl.cs @@ -9,9 +9,15 @@ using System.Windows; using System.Windows.Input; +using MaterialDesignExtensions.Commands.Internal; using MaterialDesignExtensions.Controllers; using MaterialDesignExtensions.Model; +// use Pri.LongPath classes instead of System.IO for the MaterialDesignExtensions.LongPath build to support long file system paths on older Windows and .NET versions +#if LONG_PATH +using DirectoryInfo = Pri.LongPath.DirectoryInfo; +#endif + namespace MaterialDesignExtensions.Controls { /// @@ -19,11 +25,6 @@ namespace MaterialDesignExtensions.Controls /// public class OpenDirectoryControl : FileSystemControl { - /// - /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. - /// - public static readonly RoutedCommand SelectDirectoryCommand = new RoutedCommand(); - /// /// An event raised by selecting a directory to open. /// @@ -47,13 +48,13 @@ public event RoutedEventHandler DirectorySelected } /// - /// An command called by by selecting a directory to open. + /// An command called by selecting a directory to open. /// public static readonly DependencyProperty DirectorySelectedCommandProperty = DependencyProperty.Register( nameof(DirectorySelectedCommand), typeof(ICommand), typeof(OpenDirectoryControl), new PropertyMetadata(null, null)); /// - /// An command called by by selecting a directory to open. + /// An command called by selecting a directory to open. /// public ICommand DirectorySelectedCommand { @@ -79,7 +80,7 @@ static OpenDirectoryControl() public OpenDirectoryControl() : base() { - CommandBindings.Add(new CommandBinding(SelectDirectoryCommand, SelectDirectoryCommandHandler)); + CommandBindings.Add(new CommandBinding(FileSystemControlCommands.SelectDirectoryCommand, SelectDirectoryCommandHandler)); } private void SelectDirectoryCommandHandler(object sender, ExecutedRoutedEventArgs args) @@ -131,7 +132,7 @@ protected override IEnumerable GetFileSystemEntryItems() public class DirectorySelectedEventArgs : RoutedEventArgs { /// - /// The selected directory as + /// The selected directory as . /// public DirectoryInfo DirectoryInfo { get; private set; } diff --git a/MaterialDesignExtensions/Controls/OpenDirectoryDialog.cs b/MaterialDesignExtensions/Controls/OpenDirectoryDialog.cs index 8c84e544..514eed1a 100644 --- a/MaterialDesignExtensions/Controls/OpenDirectoryDialog.cs +++ b/MaterialDesignExtensions/Controls/OpenDirectoryDialog.cs @@ -8,6 +8,11 @@ using MaterialDesignThemes.Wpf; +// use Pri.LongPath classes instead of System.IO for the MaterialDesignExtensions.LongPath build to support long file system paths on older Windows and .NET versions +#if LONG_PATH +using DirectoryInfo = Pri.LongPath.DirectoryInfo; +#endif + namespace MaterialDesignExtensions.Controls { /// @@ -71,29 +76,6 @@ private void OpenDirectoryControlDirectorySelectedHandler(object sender, RoutedE DialogHost.CloseDialogCommand.Execute(new OpenDirectoryDialogResult(false, (args as DirectorySelectedEventArgs)?.DirectoryInfo), GetDialogHost()); } - /// - /// Shows a new . - /// - /// The name of the - /// The width of the dialog (optional) - /// The heigth of the dialog (optional) - /// The current directory to show (optional) - /// Show or hide hidden files in the dialog (optional) - /// Show or hide system files in the dialog (optional) - /// Callback after openening the dialog (optional) - /// Callback after closing the dialog (optional) - /// - [Obsolete("Use the overloaded method with OpenDirectoryDialogArguments instead")] - public static async Task ShowDialogAsync(string dialogHostName, double? width = null, double? height = null, - string currentDirectory = null, - bool showHiddenFilesAndDirectories = false, bool showSystemFilesAndDirectories = false, - DialogOpenedEventHandler openedHandler = null, DialogClosingEventHandler closingHandler = null) - { - OpenDirectoryDialog dialog = InitDialog(width, height, currentDirectory, showHiddenFilesAndDirectories, showSystemFilesAndDirectories); - - return await DialogHost.Show(dialog, dialogHostName, openedHandler, closingHandler) as OpenDirectoryDialogResult; - } - /// /// Shows a new . /// @@ -106,36 +88,16 @@ public static async Task ShowDialogAsync(string dialo args.Width, args.Height, args.CurrentDirectory, + args.CreateNewDirectoryEnabled, args.ShowHiddenFilesAndDirectories, - args.ShowSystemFilesAndDirectories + args.ShowSystemFilesAndDirectories, + args.SwitchPathPartsAsButtonsEnabled, + args.PathPartsAsButtons ); return await DialogHost.Show(dialog, dialogHostName, args.OpenedHandler, args.ClosingHandler) as OpenDirectoryDialogResult; } - /// - /// Shows a new . - /// - /// The - /// The width of the dialog (optional) - /// The heigth of the dialog (optional) - /// The current directory to show (optional) - /// Show or hide hidden files in the dialog (optional) - /// Show or hide system files in the dialog (optional) - /// Callback after openening the dialog (optional) - /// Callback after closing the dialog (optional) - /// - [Obsolete("Use the overloaded method with OpenDirectoryDialogArguments instead")] - public static async Task ShowDialogAsync(DialogHost dialogHost, double? width = null, double? height = null, - string currentDirectory = null, - bool showHiddenFilesAndDirectories = false, bool showSystemFilesAndDirectories = false, - DialogOpenedEventHandler openedHandler = null, DialogClosingEventHandler closingHandler = null) - { - OpenDirectoryDialog dialog = InitDialog(width, height, currentDirectory, showHiddenFilesAndDirectories, showSystemFilesAndDirectories); - - return await dialogHost.ShowDialog(dialog, openedHandler, closingHandler) as OpenDirectoryDialogResult; - } - /// /// Shows a new . /// @@ -148,8 +110,11 @@ public static async Task ShowDialogAsync(DialogHost d args.Width, args.Height, args.CurrentDirectory, + args.CreateNewDirectoryEnabled, args.ShowHiddenFilesAndDirectories, - args.ShowSystemFilesAndDirectories + args.ShowSystemFilesAndDirectories, + args.SwitchPathPartsAsButtonsEnabled, + args.PathPartsAsButtons ); return await dialogHost.ShowDialog(dialog, args.OpenedHandler, args.ClosingHandler) as OpenDirectoryDialogResult; @@ -157,10 +122,12 @@ public static async Task ShowDialogAsync(DialogHost d private static OpenDirectoryDialog InitDialog(double? width, double? height, string currentDirectory, - bool showHiddenFilesAndDirectories, bool showSystemFilesAndDirectories) + bool createNewDirectoryEnabled, + bool showHiddenFilesAndDirectories, bool showSystemFilesAndDirectories, + bool switchPathPartsAsButtonsEnabled, bool pathPartsAsButtons) { OpenDirectoryDialog dialog = new OpenDirectoryDialog(); - InitDialog(dialog, width, height, currentDirectory, showHiddenFilesAndDirectories, showSystemFilesAndDirectories); + InitDialog(dialog, width, height, currentDirectory, showHiddenFilesAndDirectories, showSystemFilesAndDirectories, createNewDirectoryEnabled, switchPathPartsAsButtonsEnabled, pathPartsAsButtons); return dialog; } @@ -175,6 +142,13 @@ public class OpenDirectoryDialogArguments : FileSystemDialogArguments /// Creates a new . /// public OpenDirectoryDialogArguments() : base() { } + + /// + /// Copy constructor + /// + /// + public OpenDirectoryDialogArguments(OpenDirectoryDialogArguments args) + : base(args) { } } /// diff --git a/MaterialDesignExtensions/Controls/OpenFileControl.cs b/MaterialDesignExtensions/Controls/OpenFileControl.cs index 609427d9..9b20acfd 100644 --- a/MaterialDesignExtensions/Controls/OpenFileControl.cs +++ b/MaterialDesignExtensions/Controls/OpenFileControl.cs @@ -10,6 +10,13 @@ using System.Windows.Controls; using System.Windows.Input; +// use Pri.LongPath classes instead of System.IO for the MaterialDesignExtensions.LongPath build to support long file system paths on older Windows and .NET versions +#if LONG_PATH +using DirectoryInfo = Pri.LongPath.DirectoryInfo; +using FileInfo = Pri.LongPath.FileInfo; +using FileSystemInfo = Pri.LongPath.FileSystemInfo; +#endif + namespace MaterialDesignExtensions.Controls { /// @@ -39,16 +46,16 @@ protected override void CurrentFileChangedHandler(string newCurrentFile) } } - protected override void SelectFileSystemEntryCommandHandler(object sender, ExecutedRoutedEventArgs args) + protected override void SelectFileSystemEntry(FileSystemInfo fileSystemInfo) { - if (args.Parameter != null) + if (fileSystemInfo != null) { - if (args.Parameter is DirectoryInfo directoryInfo) + if (fileSystemInfo is DirectoryInfo directoryInfo) { CurrentDirectory = directoryInfo.FullName; CurrentFile = null; } - else if (args.Parameter is FileInfo fileInfo) + else if (fileSystemInfo is FileInfo fileInfo) { CurrentFile = fileInfo.FullName; } diff --git a/MaterialDesignExtensions/Controls/OpenFileDialog.cs b/MaterialDesignExtensions/Controls/OpenFileDialog.cs index ca443511..bd6ade24 100644 --- a/MaterialDesignExtensions/Controls/OpenFileDialog.cs +++ b/MaterialDesignExtensions/Controls/OpenFileDialog.cs @@ -11,6 +11,11 @@ using MaterialDesignExtensions.Converters; using MaterialDesignExtensions.Model; +// use Pri.LongPath classes instead of System.IO for the MaterialDesignExtensions.LongPath build to support long file system paths on older Windows and .NET versions +#if LONG_PATH +using FileInfo = Pri.LongPath.FileInfo; +#endif + namespace MaterialDesignExtensions.Controls { /// @@ -38,29 +43,6 @@ protected override void OpenDirectoryControlFileSelectedHandler(object sender, R DialogHost.CloseDialogCommand.Execute(new OpenFileDialogResult(false, (args as FileSelectedEventArgs)?.FileInfo), GetDialogHost()); } - /// - /// Shows a new . - /// - /// The name of the - /// The width of the dialog (optional) - /// The heigth of the dialog (optional) - /// The current directory to show (optional) - /// Show or hide hidden files in the dialog (optional) - /// Show or hide system files in the dialog (optional) - /// Callback after openening the dialog (optional) - /// Callback after closing the dialog (optional) - /// - [Obsolete("Use the overloaded method with OpenFileDialogArguments instead")] - public static async Task ShowDialogAsync(string dialogHostName, double? width = null, double? height = null, - string currentDirectory = null, - bool showHiddenFilesAndDirectories = false, bool showSystemFilesAndDirectories = false, - DialogOpenedEventHandler openedHandler = null, DialogClosingEventHandler closingHandler = null) - { - OpenFileDialog dialog = InitDialog(width, height, currentDirectory, null, -1, showHiddenFilesAndDirectories, showSystemFilesAndDirectories); - - return await DialogHost.Show(dialog, dialogHostName, openedHandler, closingHandler) as OpenFileDialogResult; - } - /// /// Shows a new . /// @@ -76,35 +58,14 @@ public static async Task ShowDialogAsync(string dialogHost args.Filters, args.FilterIndex, args.ShowHiddenFilesAndDirectories, - args.ShowSystemFilesAndDirectories + args.ShowSystemFilesAndDirectories, + args.SwitchPathPartsAsButtonsEnabled, + args.PathPartsAsButtons ); return await DialogHost.Show(dialog, dialogHostName, args.OpenedHandler, args.ClosingHandler) as OpenFileDialogResult; } - /// - /// Shows a new . - /// - /// The - /// The width of the dialog (optional) - /// The heigth of the dialog (optional) - /// The current directory to show (optional) - /// Show or hide hidden files in the dialog (optional) - /// Show or hide system files in the dialog (optional) - /// Callback after openening the dialog (optional) - /// Callback after closing the dialog (optional) - /// - [Obsolete("Use the overloaded method with OpenFileDialogArguments instead")] - public static async Task ShowDialogAsync(DialogHost dialogHost, double? width = null, double? height = null, - string currentDirectory = null, - bool showHiddenFilesAndDirectories = false, bool showSystemFilesAndDirectories = false, - DialogOpenedEventHandler openedHandler = null, DialogClosingEventHandler closingHandler = null) - { - OpenFileDialog dialog = InitDialog(width, height, currentDirectory, null, -1, showHiddenFilesAndDirectories, showSystemFilesAndDirectories); - - return await dialogHost.ShowDialog(dialog, openedHandler, closingHandler) as OpenFileDialogResult; - } - /// /// Shows a new . /// @@ -120,7 +81,9 @@ public static async Task ShowDialogAsync(DialogHost dialog args.Filters, args.FilterIndex, args.ShowHiddenFilesAndDirectories, - args.ShowSystemFilesAndDirectories + args.ShowSystemFilesAndDirectories, + args.SwitchPathPartsAsButtonsEnabled, + args.PathPartsAsButtons ); return await dialogHost.ShowDialog(dialog, args.OpenedHandler, args.ClosingHandler) as OpenFileDialogResult; @@ -129,10 +92,11 @@ public static async Task ShowDialogAsync(DialogHost dialog private static OpenFileDialog InitDialog(double? width, double? height, string currentDirectory, string filters, int filterIndex, - bool showHiddenFilesAndDirectories, bool showSystemFilesAndDirectories) + bool showHiddenFilesAndDirectories, bool showSystemFilesAndDirectories, + bool switchPathPartsAsButtonsEnabled, bool pathPartsAsButtons) { OpenFileDialog dialog = new OpenFileDialog(); - InitDialog(dialog, width, height, currentDirectory, showHiddenFilesAndDirectories, showSystemFilesAndDirectories); + InitDialog(dialog, width, height, currentDirectory, showHiddenFilesAndDirectories, showSystemFilesAndDirectories, switchPathPartsAsButtonsEnabled, pathPartsAsButtons); dialog.Filters = new FileFiltersTypeConverter().ConvertFrom(null, null, filters) as IList; dialog.FilterIndex = filterIndex; @@ -149,6 +113,13 @@ public class OpenFileDialogArguments : FileDialogArguments /// Creates a new . /// public OpenFileDialogArguments() : base() { } + + /// + /// Copy constructor + /// + /// + public OpenFileDialogArguments(OpenFileDialogArguments args) + : base(args) { } } /// diff --git a/MaterialDesignExtensions/Controls/OpenMultipleDirectoriesControl.cs b/MaterialDesignExtensions/Controls/OpenMultipleDirectoriesControl.cs new file mode 100644 index 00000000..40092de0 --- /dev/null +++ b/MaterialDesignExtensions/Controls/OpenMultipleDirectoriesControl.cs @@ -0,0 +1,271 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; + +using MaterialDesignThemes.Wpf; + +using MaterialDesignExtensions.Commands.Internal; +using MaterialDesignExtensions.Controllers; +using MaterialDesignExtensions.Model; + +// use Pri.LongPath classes instead of System.IO for the MaterialDesignExtensions.LongPath build to support long file system paths on older Windows and .NET versions +#if LONG_PATH +using DirectoryInfo = Pri.LongPath.DirectoryInfo; +#endif + +namespace MaterialDesignExtensions.Controls +{ + /// + /// A control for selecting multiple directories. + /// + public class OpenMultipleDirectoriesControl : FileSystemControl + { + private const string SelectionItemsControlName = "selectionItemsControl"; + private const string EmptySelectionTextBlockName = "emptySelectionTextBlock"; + + /// + /// An event raised by selecting directories to open. + /// + public static readonly RoutedEvent DirectoriesSelectedEvent = EventManager.RegisterRoutedEvent( + nameof(DirectoriesSelected), RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(OpenMultipleDirectoriesControl)); + + /// + /// An event raised by selecting directories to open. + /// + public event RoutedEventHandler DirectoriesSelected + { + add + { + AddHandler(DirectoriesSelectedEvent, value); + } + + remove + { + RemoveHandler(DirectoriesSelectedEvent, value); + } + } + + /// + /// An command called by selecting directories to open. + /// + public static readonly DependencyProperty DirectoriesSelectedCommandProperty = DependencyProperty.Register( + nameof(DirectoriesSelectedCommand), typeof(ICommand), typeof(OpenMultipleDirectoriesControl), new PropertyMetadata(null, null)); + + /// + /// An command called by selecting directories to open. + /// + public ICommand DirectoriesSelectedCommand + { + get + { + return (ICommand)GetValue(DirectoriesSelectedCommandProperty); + } + + set + { + SetValue(DirectoriesSelectedCommandProperty, value); + } + } + + private ItemsControl m_selectionItemsControl; + private TextBlock m_emptySelectionTextBlock; + + static OpenMultipleDirectoriesControl() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(OpenMultipleDirectoriesControl), new FrameworkPropertyMetadata(typeof(OpenMultipleDirectoriesControl))); + } + + /// + /// Creates a new . + /// + public OpenMultipleDirectoriesControl() + : base() + { + + m_selectionItemsControl = null; + m_emptySelectionTextBlock = null; + + CommandBindings.Add(new CommandBinding(FileSystemControlCommands.OpenSelectionDrawerCommand, OpenSelectionDrawerCommandHandler)); + CommandBindings.Add(new CommandBinding(FileSystemControlCommands.SelectDirectoryCommand, SelectDirectoryCommandHandler)); + CommandBindings.Add(new CommandBinding(FileSystemControlCommands.SelectMultipleDirectoriesCommand, SelectMultipleDirectoriesCommandHandler, CanExecuteSelectMultipleDirectoriesCommand)); + } + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + m_selectionItemsControl = Template.FindName(SelectionItemsControlName, this) as ItemsControl; + + m_emptySelectionTextBlock = Template.FindName(EmptySelectionTextBlockName, this) as TextBlock; + + UpdateSelectionList(); + + UpdateSelectionListVisibility(); + } + + private void OpenSelectionDrawerCommandHandler(object sender, ExecutedRoutedEventArgs args) + { + DrawerHost drawerHost = (DrawerHost)Template.FindName(DrawerHostName, this); + drawerHost.IsRightDrawerOpen = true; + } + + private void SelectDirectoryCommandHandler(object sender, ExecutedRoutedEventArgs args) + { + if (args.Parameter != null) + { + if (args.Parameter is DirectoryInfo directoryInfo) + { + m_controller.SelectOrRemoveDirectoryForMultipleSelection(directoryInfo); + } + } + } + + private void SelectMultipleDirectoriesCommandHandler(object sender, ExecutedRoutedEventArgs args) + { + DirectoriesSelectedEventArgs eventArgs = new DirectoriesSelectedEventArgs(DirectoriesSelectedEvent, this, m_controller.SelectedDirectories.ToList()); + RaiseEvent(eventArgs); + + if (DirectoriesSelectedCommand != null && DirectoriesSelectedCommand.CanExecute(eventArgs.DirectoryInfoList)) + { + DirectoriesSelectedCommand.Execute(eventArgs.DirectoryInfoList); + } + } + + private void CanExecuteSelectMultipleDirectoriesCommand(object sender, CanExecuteRoutedEventArgs args) + { + args.CanExecute = m_controller != null && m_controller.SelectedDirectories != null && m_controller.SelectedDirectories.Any(); + } + + protected override void ControllerPropertyChangedHandler(object sender, PropertyChangedEventArgs args) + { + if (sender == m_controller) + { + if (args.PropertyName == nameof(FileSystemController.Directories)) + { + m_fileSystemEntryItemsControl.ItemsSource = GetFileSystemEntryItems(); + + if (m_controller.Directories != null && m_controller.Directories.Any()) + { + ItemsScrollViewer?.ScrollToTop(); + } + + UpdateListVisibility(); + } + else if (args.PropertyName == nameof(FileSystemController.SelectedDirectories)) + { + UpdateSelection(); + UpdateSelectionList(); + + UpdateSelectionListVisibility(); + } + } + + base.ControllerPropertyChangedHandler(sender, args); + } + + protected override IEnumerable GetFileSystemEntryItems() + { + ISet directoryNames = new HashSet(m_controller.SelectedDirectories.Select(directoryInfo => directoryInfo.FullName.ToLower())); + + return m_controller.Directories + .Select(directory => + { + bool isSelected = directoryNames.Contains(directory.FullName.ToLower()); + + return new DirectoryInfoItem() { IsSelected = isSelected, Value = directory }; + }) + // call ToList() to run the LINQ expression immediately, otherwise WPF will behave some kind of strange + .ToList(); + } + + protected override void UpdateSelection() + { + IEnumerable items = m_fileSystemEntryItemsControl?.ItemsSource; + + if (items != null) + { + ISet directoryNames = new HashSet(m_controller.SelectedDirectories.Select(directoryInfo => directoryInfo.FullName.ToLower())); + + foreach (object item in items) + { + if (item is DirectoryInfoItem directoryInfoItem) + { + directoryInfoItem.IsSelected = directoryNames.Contains(directoryInfoItem.Value.FullName.ToLower()); + } + } + } + } + + private void UpdateSelectionList() + { + m_selectionItemsControl.ItemsSource = m_controller.SelectedDirectories + .OrderBy(directory => directory.Name.ToLower()) + .ThenBy(directory => directory.Parent.FullName.ToLower()); + } + + /// + /// Shows the list of the selected directories or hides it if it is empty. A message will be show instead of an empty list. + /// + protected void UpdateSelectionListVisibility() + { + if (m_selectionItemsControl != null + && m_selectionItemsControl.ItemsSource != null + && m_selectionItemsControl.ItemsSource.GetEnumerator().MoveNext()) + { + m_selectionItemsControl.Visibility = Visibility.Visible; + m_emptySelectionTextBlock.Visibility = Visibility.Collapsed; + } + else + { + m_selectionItemsControl.Visibility = Visibility.Collapsed; + m_emptySelectionTextBlock.Visibility = Visibility.Visible; + } + } + } + + /// + /// The arguments for the event. + /// + public class DirectoriesSelectedEventArgs : RoutedEventArgs + { + /// + /// The selected directories as . + /// + public List DirectoryInfoList { get; private set; } + + /// + /// The selected directories as full filename string. + /// + public List Directories + { + get + { + return DirectoryInfoList + .Select(directoryInfo => directoryInfo.FullName) + .ToList(); + } + } + + /// + /// Creates a new . + /// + /// + /// The source object + /// The list of selected directories + public DirectoriesSelectedEventArgs(RoutedEvent routedEvent, object source, List directoryInfoList) + : base(routedEvent, source) + { + DirectoryInfoList = directoryInfoList + .OrderBy(directoryInfo => directoryInfo.FullName.ToLower()) + .ToList(); + } + } +} diff --git a/MaterialDesignExtensions/Controls/OpenMultipleDirectoriesDialog.cs b/MaterialDesignExtensions/Controls/OpenMultipleDirectoriesDialog.cs new file mode 100644 index 00000000..0ad4b7cd --- /dev/null +++ b/MaterialDesignExtensions/Controls/OpenMultipleDirectoriesDialog.cs @@ -0,0 +1,186 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; + +using MaterialDesignThemes.Wpf; + +// use Pri.LongPath classes instead of System.IO for the MaterialDesignExtensions.LongPath build to support long file system paths on older Windows and .NET versions +#if LONG_PATH +using DirectoryInfo = Pri.LongPath.DirectoryInfo; +#endif + +namespace MaterialDesignExtensions.Controls +{ + /// + /// A dialog for selecting multiple directories. + /// + public class OpenMultipleDirectoriesDialog : FileSystemDialog + { + private static readonly string OpenMultipleDirectoriesControlName = "openMultipleDirectoriesControl"; + + private OpenMultipleDirectoriesControl m_openMultipleDirectoriesControl; + + static OpenMultipleDirectoriesDialog() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(OpenMultipleDirectoriesDialog), new FrameworkPropertyMetadata(typeof(OpenMultipleDirectoriesDialog))); + } + + /// + /// Creates a new . + /// + public OpenMultipleDirectoriesDialog() + : base() + { + m_openMultipleDirectoriesControl = null; + + Loaded += LoadedHandler; + Unloaded += UnloadedHandler; + } + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + if (m_openMultipleDirectoriesControl != null) + { + m_openMultipleDirectoriesControl.Cancel -= CancelHandler; + m_openMultipleDirectoriesControl.DirectoriesSelected -= OpenMultipleDirectoriesControlDirectoriesSelectedHandler; + } + + m_openMultipleDirectoriesControl = Template.FindName(OpenMultipleDirectoriesControlName, this) as OpenMultipleDirectoriesControl; + } + + private void LoadedHandler(object sender, RoutedEventArgs args) + { + m_openMultipleDirectoriesControl.Cancel += CancelHandler; + m_openMultipleDirectoriesControl.DirectoriesSelected += OpenMultipleDirectoriesControlDirectoriesSelectedHandler; + } + + private void UnloadedHandler(object sender, RoutedEventArgs args) + { + m_openMultipleDirectoriesControl.Cancel -= CancelHandler; + m_openMultipleDirectoriesControl.DirectoriesSelected -= OpenMultipleDirectoriesControlDirectoriesSelectedHandler; + } + + private void CancelHandler(object sender, RoutedEventArgs args) + { + DialogHost.CloseDialogCommand.Execute(new OpenMultipleDirectoriesDialogResult(true, null), GetDialogHost()); + } + + private void OpenMultipleDirectoriesControlDirectoriesSelectedHandler(object sender, RoutedEventArgs args) + { + DialogHost.CloseDialogCommand.Execute(new OpenMultipleDirectoriesDialogResult(false, (args as DirectoriesSelectedEventArgs)?.DirectoryInfoList), GetDialogHost()); + } + + /// + /// Shows a new . + /// + /// The name of the + /// The arguments for the dialog initialization + /// + public static async Task ShowDialogAsync(string dialogHostName, OpenMultipleDirectoriesDialogArguments args) + { + OpenMultipleDirectoriesDialog dialog = InitDialog( + args.Width, + args.Height, + args.CurrentDirectory, + args.CreateNewDirectoryEnabled, + args.ShowHiddenFilesAndDirectories, + args.ShowSystemFilesAndDirectories, + args.SwitchPathPartsAsButtonsEnabled, + args.PathPartsAsButtons + ); + + return await DialogHost.Show(dialog, dialogHostName, args.OpenedHandler, args.ClosingHandler) as OpenMultipleDirectoriesDialogResult; + } + + /// + /// Shows a new . + /// + /// The + /// The arguments for the dialog initialization + /// + public static async Task ShowDialogAsync(DialogHost dialogHost, OpenMultipleDirectoriesDialogArguments args) + { + OpenMultipleDirectoriesDialog dialog = InitDialog( + args.Width, + args.Height, + args.CurrentDirectory, + args.CreateNewDirectoryEnabled, + args.ShowHiddenFilesAndDirectories, + args.ShowSystemFilesAndDirectories, + args.SwitchPathPartsAsButtonsEnabled, + args.PathPartsAsButtons + ); + + return await dialogHost.ShowDialog(dialog, args.OpenedHandler, args.ClosingHandler) as OpenMultipleDirectoriesDialogResult; + } + + private static OpenMultipleDirectoriesDialog InitDialog(double? width, double? height, + string currentDirectory, + bool createNewDirectoryEnabled, + bool showHiddenFilesAndDirectories, bool showSystemFilesAndDirectories, + bool switchPathPartsAsButtonsEnabled, bool pathPartsAsButtons) + { + OpenMultipleDirectoriesDialog dialog = new OpenMultipleDirectoriesDialog(); + InitDialog(dialog, width, height, currentDirectory, showHiddenFilesAndDirectories, showSystemFilesAndDirectories, createNewDirectoryEnabled, switchPathPartsAsButtonsEnabled, pathPartsAsButtons); + + return dialog; + } + } + + /// + /// Arguments to initialize an open multiple directories dialog. + /// + public class OpenMultipleDirectoriesDialogArguments : FileSystemDialogArguments + { + /// + /// Creates a new . + /// + public OpenMultipleDirectoriesDialogArguments() : base() { } + } + + /// + /// The dialog result for . + /// + public class OpenMultipleDirectoriesDialogResult : FileSystemDialogResult + { + /// + /// The selected directories as . + /// + public List DirectoryInfoList { get; private set; } + + /// + /// The selected directories as full filename string. + /// + public List Directories + { + get + { + return DirectoryInfoList + .Select(directoryInfo => directoryInfo.FullName) + .ToList(); + } + } + + /// + /// Creates a new . + /// + /// True if the dialog was canceled + /// The list of selected directories + public OpenMultipleDirectoriesDialogResult(bool canceled, List directoryInfoList) + : base(canceled) + { + if (directoryInfoList != null) + { + DirectoryInfoList = directoryInfoList + .OrderBy(directoryInfo => directoryInfo.FullName.ToLower()) + .ToList(); + } + } + } +} diff --git a/MaterialDesignExtensions/Controls/OpenMultipleFilesControl.cs b/MaterialDesignExtensions/Controls/OpenMultipleFilesControl.cs new file mode 100644 index 00000000..69533808 --- /dev/null +++ b/MaterialDesignExtensions/Controls/OpenMultipleFilesControl.cs @@ -0,0 +1,450 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; + +using MaterialDesignThemes.Wpf; + +using MaterialDesignExtensions.Commands.Internal; +using MaterialDesignExtensions.Controllers; +using MaterialDesignExtensions.Converters; +using MaterialDesignExtensions.Model; + +// use Pri.LongPath classes instead of System.IO for the MaterialDesignExtensions.LongPath build to support long file system paths on older Windows and .NET versions +#if LONG_PATH +using FileSystemInfo = Pri.LongPath.FileSystemInfo; +using DirectoryInfo = Pri.LongPath.DirectoryInfo; +using FileInfo = Pri.LongPath.FileInfo; +#endif + +namespace MaterialDesignExtensions.Controls +{ + /// + /// A control for selecting multiple files. + /// + public class OpenMultipleFilesControl : FileSystemControl + { + private const string FileFiltersComboBoxName = "fileFiltersComboBox"; + private const string SelectionItemsControlName = "selectionItemsControl"; + private const string EmptySelectionTextBlockName = "emptySelectionTextBlock"; + + /// + /// An event raised by selecting files to open. + /// + public static readonly RoutedEvent FilesSelectedEvent = EventManager.RegisterRoutedEvent( + nameof(FilesSelected), RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(OpenMultipleFilesControl)); + + /// + /// An event raised by selecting files to open. + /// + public event RoutedEventHandler FilesSelected + { + add + { + AddHandler(FilesSelectedEvent, value); + } + + remove + { + RemoveHandler(FilesSelectedEvent, value); + } + } + + /// + /// True, to clear the cache during unloading the control. + /// + public static readonly DependencyProperty ClearCacheOnUnloadProperty = DependencyProperty.Register( + nameof(ClearCacheOnUnload), typeof(bool), typeof(OpenMultipleFilesControl), new PropertyMetadata(true)); + + /// + /// True, to clear the cache during unloading the control. + /// + public bool ClearCacheOnUnload + { + get + { + return (bool)GetValue(ClearCacheOnUnloadProperty); + } + + set + { + SetValue(ClearCacheOnUnloadProperty, value); + } + } + + /// + /// An command called by selecting files to open. + /// + public static readonly DependencyProperty FilesSelectedCommandProperty = DependencyProperty.Register( + nameof(FilesSelectedCommand), typeof(ICommand), typeof(OpenMultipleFilesControl), new PropertyMetadata(null, null)); + + /// + /// An command called by selecting files to open. + /// + public ICommand FilesSelectedCommand + { + get + { + return (ICommand)GetValue(FilesSelectedCommandProperty); + } + + set + { + SetValue(FilesSelectedCommandProperty, value); + } + } + + /// + /// The possible file filters to select from for applying to the files inside the current directory. + /// Strings according to the original .NET API will be converted automatically + /// (see https://docs.microsoft.com/de-de/dotnet/api/microsoft.win32.filedialog.filter?view=netframework-4.7.1#Microsoft_Win32_FileDialog_Filter). + /// + public static readonly DependencyProperty FiltersProperty = DependencyProperty.Register( + nameof(Filters), typeof(IList), typeof(OpenMultipleFilesControl), new PropertyMetadata(null, FiltersChangedHandler)); + + /// + /// The possible file filters to select from for applying to the files inside the current directory. + /// Strings according to the original .NET API will be converted automatically + /// (see https://docs.microsoft.com/de-de/dotnet/api/microsoft.win32.filedialog.filter?view=netframework-4.7.1#Microsoft_Win32_FileDialog_Filter). + /// + [TypeConverter(typeof(FileFiltersTypeConverter))] + public IList Filters + { + get + { + return (IList)GetValue(FiltersProperty); + } + + set + { + SetValue(FiltersProperty, value); + } + } + + /// + /// The index of the file filter to apply to the files inside the current directory. + /// + public static readonly DependencyProperty FilterIndexProperty = DependencyProperty.Register( + nameof(FilterIndex), typeof(int), typeof(OpenMultipleFilesControl), new PropertyMetadata(0, FiltersChangedHandler)); + + /// + /// The index of the file filter to apply to the files inside the current directory. + /// + public int FilterIndex + { + get + { + return (int)GetValue(FilterIndexProperty); + } + + set + { + SetValue(FilterIndexProperty, value); + } + } + + /// + /// Shows folders and files as a group with a header. + /// + public static readonly DependencyProperty GroupFoldersAndFilesProperty = DependencyProperty.Register( + nameof(GroupFoldersAndFiles), typeof(bool), typeof(OpenMultipleFilesControl), new PropertyMetadata(true)); + + /// + /// Shows folders and files as a group with a header. + /// + public bool GroupFoldersAndFiles + { + get + { + return (bool)GetValue(GroupFoldersAndFilesProperty); + } + + set + { + SetValue(GroupFoldersAndFilesProperty, value); + } + } + + private ComboBox m_fileFiltersComboBox; + private ItemsControl m_selectionItemsControl; + private TextBlock m_emptySelectionTextBlock; + + static OpenMultipleFilesControl() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(OpenMultipleFilesControl), new FrameworkPropertyMetadata(typeof(OpenMultipleFilesControl))); + } + + /// + /// Creates a new . + /// + public OpenMultipleFilesControl() + : base() + { + m_fileFiltersComboBox = null; + m_selectionItemsControl = null; + m_emptySelectionTextBlock = null; + + CommandBindings.Add(new CommandBinding(FileSystemControlCommands.OpenSelectionDrawerCommand, OpenSelectionDrawerCommandHandler)); + CommandBindings.Add(new CommandBinding(FileSystemControlCommands.SelectFileCommand, SelectFileCommandHandler)); + CommandBindings.Add(new CommandBinding(FileSystemControlCommands.SelectMultipleFilesCommand, SelectMultipleFilesCommandHandler, CanExecuteSelectMultipleFilesCommand)); + } + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + m_fileFiltersComboBox = Template.FindName(FileFiltersComboBoxName, this) as ComboBox; + m_fileFiltersComboBox.ItemsSource = Filters; + + m_selectionItemsControl = Template.FindName(SelectionItemsControlName, this) as ItemsControl; + + m_emptySelectionTextBlock = Template.FindName(EmptySelectionTextBlockName, this) as TextBlock; + + UpdateFileFiltersVisibility(); + UpdateSelectionList(); + + UpdateSelectionListVisibility(); + } + + protected override void UnloadedHandler(object sender, RoutedEventArgs args) + { + if (ClearCacheOnUnload) + { + BitmapImageHelper.ClearCache(); + } + + base.UnloadedHandler(sender, args); + } + + private void OpenSelectionDrawerCommandHandler(object sender, ExecutedRoutedEventArgs args) + { + DrawerHost drawerHost = (DrawerHost)Template.FindName(DrawerHostName, this); + drawerHost.IsRightDrawerOpen = true; + } + + private void SelectFileCommandHandler(object sender, ExecutedRoutedEventArgs args) + { + try + { + if (args.Parameter != null) + { + if (args.Parameter is FileInfo fileInfo) + { + m_controller.SelectOrRemoveFileForMultipleSelection(fileInfo); + } + } + } + catch (PathTooLongException) + { + SnackbarMessageQueue.Enqueue(Localization.Strings.LongPathsAreNotSupported); + } + } + + private void SelectMultipleFilesCommandHandler(object sender, ExecutedRoutedEventArgs args) + { + FilesSelectedEventArgs eventArgs = new FilesSelectedEventArgs(FilesSelectedEvent, this, m_controller.SelectedFiles.ToList()); + RaiseEvent(eventArgs); + + if (FilesSelectedCommand != null && FilesSelectedCommand.CanExecute(eventArgs.FileInfoList)) + { + FilesSelectedCommand.Execute(eventArgs.FileInfoList); + } + } + + private void CanExecuteSelectMultipleFilesCommand(object sender, CanExecuteRoutedEventArgs args) + { + args.CanExecute = m_controller != null && m_controller.SelectedFiles != null && m_controller.SelectedFiles.Any(); + } + + private static void FiltersChangedHandler(DependencyObject obj, DependencyPropertyChangedEventArgs args) + { + if (args.Property == FiltersProperty) + { + (obj as OpenMultipleFilesControl)?.FileFiltersChangedHandler(args.NewValue as IList); + } + else if (args.Property == FilterIndexProperty) + { + (obj as OpenMultipleFilesControl)?.FileFiltersChangedHandler((int)args.NewValue); + } + } + + private void FileFiltersChangedHandler(IList newFilters) + { + FileFiltersChangedHandler(newFilters, FilterIndex); + } + + private void FileFiltersChangedHandler(int newFilterIndex) + { + FileFiltersChangedHandler(Filters, newFilterIndex); + } + + private void FileFiltersChangedHandler(IList newFilters, int newFilterIndex) + { + IFileFilter fileFilter = null; + + if (newFilters != null && newFilterIndex >= 0 && newFilterIndex < newFilters.Count) + { + fileFilter = newFilters[FilterIndex]; + } + + m_controller.SetFileFilter(newFilters, fileFilter); + } + + protected override void ControllerPropertyChangedHandler(object sender, PropertyChangedEventArgs args) + { + if (sender == m_controller) + { + if (args.PropertyName == nameof(FileSystemController.DirectoriesAndFiles)) + { + List items = m_controller.DirectoriesAndFiles; + m_fileSystemEntryItemsControl.ItemsSource = GetFileSystemEntryItems(); + + if (items != null && items.Any()) + { + ItemsScrollViewer?.ScrollToTop(); + } + + UpdateListVisibility(); + } + else if (args.PropertyName == nameof(FileSystemController.FileFilters)) + { + Filters = m_controller.FileFilters; + m_fileFiltersComboBox.ItemsSource = m_controller.FileFilters; + + UpdateFileFiltersVisibility(); + } + else if (args.PropertyName == nameof(FileSystemController.FileFilterToApply)) + { + int filterIndex = -1; + + if (m_controller.FileFilterToApply != null && m_controller.FileFilters != null) + { + for (int i = 0; i < m_controller.FileFilters.Count && filterIndex == -1; i++) + { + if (m_controller.FileFilters[i] == m_controller.FileFilterToApply) + { + filterIndex = i; + } + } + } + + FilterIndex = filterIndex; + } + else if (args.PropertyName == nameof(FileSystemController.SelectedFiles)) + { + UpdateSelection(); + UpdateSelectionList(); + UpdateSelectionListVisibility(); + } + } + + base.ControllerPropertyChangedHandler(sender, args); + } + + protected override IEnumerable GetFileSystemEntryItems() + { + ISet fileNames = new HashSet(m_controller.SelectedFiles.Select(fileInfo => fileInfo.FullName.ToLower())); + + return FileControlHelper.GetFileSystemEntryItems( + m_controller.DirectoriesAndFiles, + m_controller, + GroupFoldersAndFiles, + fileInfo => fileNames.Contains(fileInfo.FullName.ToLower()) + ); + } + + protected void UpdateFileFiltersVisibility() + { + FileControlHelper.UpdateFileFiltersVisibility(m_fileFiltersComboBox); + } + + protected override void UpdateSelection() + { + IEnumerable items = m_fileSystemEntryItemsControl?.ItemsSource; + + if (items != null) + { + ISet fileNames = new HashSet(m_controller.SelectedFiles.Select(fileInfo => fileInfo.FullName.ToLower())); + + foreach (object item in items) + { + if (item is FileInfoItem fileInfoItem) + { + fileInfoItem.IsSelected = fileNames.Contains(fileInfoItem.Value.FullName.ToLower()); + } + } + } + } + + private void UpdateSelectionList() + { + m_selectionItemsControl.ItemsSource = m_controller.SelectedFiles + .OrderBy(file => file.Name.ToLower()) + .ThenBy(file => file.Directory.FullName.ToLower()); + } + + /// + /// Shows the list of the selected directories or hides it if it is empty. A message will be show instead of an empty list. + /// + protected void UpdateSelectionListVisibility() + { + if (m_selectionItemsControl != null + && m_selectionItemsControl.ItemsSource != null + && m_selectionItemsControl.ItemsSource.GetEnumerator().MoveNext()) + { + m_selectionItemsControl.Visibility = Visibility.Visible; + m_emptySelectionTextBlock.Visibility = Visibility.Collapsed; + } + else + { + m_selectionItemsControl.Visibility = Visibility.Collapsed; + m_emptySelectionTextBlock.Visibility = Visibility.Visible; + } + } + } + + /// + /// The arguments for the event. + /// + public class FilesSelectedEventArgs : RoutedEventArgs + { + /// + /// The selected files as . + /// + public List FileInfoList { get; private set; } + + /// + /// The selected files as full filename string. + /// + public List Files + { + get + { + return FileInfoList + .Select(fileInfo => fileInfo.FullName) + .ToList(); + } + } + + /// + /// Creates a new . + /// + /// + /// The source object + /// The list selected files + public FilesSelectedEventArgs(RoutedEvent routedEvent, object source, List fileInfoList) + : base(routedEvent, source) + { + FileInfoList = fileInfoList + .OrderBy(directoryInfo => directoryInfo.FullName.ToLower()) + .ToList(); + } + } +} diff --git a/MaterialDesignExtensions/Controls/OpenMultipleFilesDialog.cs b/MaterialDesignExtensions/Controls/OpenMultipleFilesDialog.cs new file mode 100644 index 00000000..efb11248 --- /dev/null +++ b/MaterialDesignExtensions/Controls/OpenMultipleFilesDialog.cs @@ -0,0 +1,261 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; + +using MaterialDesignThemes.Wpf; + +using MaterialDesignExtensions.Converters; +using MaterialDesignExtensions.Model; + +// use Pri.LongPath classes instead of System.IO for the MaterialDesignExtensions.LongPath build to support long file system paths on older Windows and .NET versions +#if LONG_PATH +using FileInfo = Pri.LongPath.FileInfo; +#endif + +namespace MaterialDesignExtensions.Controls +{ + /// + /// A dialog for selecting multiple files. + /// + public class OpenMultipleFilesDialog : FileSystemDialog + { + private static readonly string OpenMultipleFilesControlName = "openMultipleFilesControl"; + + /// + /// The possible file filters to select from for applying to the files inside the current directory. + /// Strings according to the original .NET API will be converted automatically + /// (see https://docs.microsoft.com/de-de/dotnet/api/microsoft.win32.filedialog.filter?view=netframework-4.7.1#Microsoft_Win32_FileDialog_Filter). + /// + public static readonly DependencyProperty FiltersProperty = DependencyProperty.Register( + nameof(Filters), typeof(IList), typeof(OpenMultipleFilesDialog), new PropertyMetadata(null)); + + /// + /// The possible file filters to select from for applying to the files inside the current directory. + /// Strings according to the original .NET API will be converted automatically + /// (see https://docs.microsoft.com/de-de/dotnet/api/microsoft.win32.filedialog.filter?view=netframework-4.7.1#Microsoft_Win32_FileDialog_Filter). + /// + [TypeConverter(typeof(FileFiltersTypeConverter))] + public IList Filters + { + get + { + return (IList)GetValue(FiltersProperty); + } + + set + { + SetValue(FiltersProperty, value); + } + } + + /// + /// The index of the file filter to apply to the files inside the current directory. + /// + public static readonly DependencyProperty FilterIndexProperty = DependencyProperty.Register( + nameof(FilterIndex), typeof(int), typeof(OpenMultipleFilesDialog), new PropertyMetadata(0)); + + /// + /// The index of the file filter to apply to the files inside the current directory. + /// + public int FilterIndex + { + get + { + return (int)GetValue(FilterIndexProperty); + } + + set + { + SetValue(FilterIndexProperty, value); + } + } + + private OpenMultipleFilesControl m_openMultipleFilesControl; + + static OpenMultipleFilesDialog() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(OpenMultipleFilesDialog), new FrameworkPropertyMetadata(typeof(OpenMultipleFilesDialog))); + } + + /// + /// Creates a new . + /// + public OpenMultipleFilesDialog() + : base() + { + m_openMultipleFilesControl = null; + + Loaded += LoadedHandler; + Unloaded += UnloadedHandler; + } + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + if (m_openMultipleFilesControl != null) + { + m_openMultipleFilesControl.Cancel -= CancelHandler; + m_openMultipleFilesControl.FilesSelected -= OpenMultipleFilesControlFilesSelectedHandler; + } + + m_openMultipleFilesControl = Template.FindName(OpenMultipleFilesControlName, this) as OpenMultipleFilesControl; + } + + private void LoadedHandler(object sender, RoutedEventArgs args) + { + m_openMultipleFilesControl.Cancel += CancelHandler; + m_openMultipleFilesControl.FilesSelected += OpenMultipleFilesControlFilesSelectedHandler; + } + + private void UnloadedHandler(object sender, RoutedEventArgs args) + { + m_openMultipleFilesControl.Cancel -= CancelHandler; + m_openMultipleFilesControl.FilesSelected -= OpenMultipleFilesControlFilesSelectedHandler; + } + + private void CancelHandler(object sender, RoutedEventArgs args) + { + DialogHost.CloseDialogCommand.Execute(new OpenMultipleFilesDialogResult(true, null), GetDialogHost()); + } + + private void OpenMultipleFilesControlFilesSelectedHandler(object sender, RoutedEventArgs args) + { + DialogHost.CloseDialogCommand.Execute(new OpenMultipleFilesDialogResult(false, (args as FilesSelectedEventArgs)?.FileInfoList), GetDialogHost()); + } + + /// + /// Shows a new . + /// + /// The name of the + /// The arguments for the dialog initialization + /// + public static async Task ShowDialogAsync(string dialogHostName, OpenMultipleFilesDialogArguments args) + { + OpenMultipleFilesDialog dialog = InitDialog( + args.Width, + args.Height, + args.CurrentDirectory, + args.Filters, + args.FilterIndex, + args.ShowHiddenFilesAndDirectories, + args.ShowSystemFilesAndDirectories, + args.SwitchPathPartsAsButtonsEnabled, + args.PathPartsAsButtons + ); + + return await DialogHost.Show(dialog, dialogHostName, args.OpenedHandler, args.ClosingHandler) as OpenMultipleFilesDialogResult; + } + + /// + /// Shows a new . + /// + /// The + /// The arguments for the dialog initialization + /// + public static async Task ShowDialogAsync(DialogHost dialogHost, OpenMultipleFilesDialogArguments args) + { + OpenMultipleFilesDialog dialog = InitDialog( + args.Width, + args.Height, + args.CurrentDirectory, + args.Filters, + args.FilterIndex, + args.ShowHiddenFilesAndDirectories, + args.ShowSystemFilesAndDirectories, + args.SwitchPathPartsAsButtonsEnabled, + args.PathPartsAsButtons + ); + + return await dialogHost.ShowDialog(dialog, args.OpenedHandler, args.ClosingHandler) as OpenMultipleFilesDialogResult; + } + + private static OpenMultipleFilesDialog InitDialog(double? width, double? height, + string currentDirectory, + string filters, int filterIndex, + bool showHiddenFilesAndDirectories, bool showSystemFilesAndDirectories, + bool switchPathPartsAsButtonsEnabled, bool pathPartsAsButtons) + { + OpenMultipleFilesDialog dialog = new OpenMultipleFilesDialog(); + InitDialog(dialog, width, height, currentDirectory, showHiddenFilesAndDirectories, showSystemFilesAndDirectories, switchPathPartsAsButtonsEnabled, pathPartsAsButtons); + dialog.Filters = new FileFiltersTypeConverter().ConvertFrom(null, null, filters) as IList; + dialog.FilterIndex = filterIndex; + + return dialog; + } + } + + /// + /// Arguments to initialize an open multiple files dialog. + /// + public class OpenMultipleFilesDialogArguments : FileSystemDialogArguments + { + /// + /// The possible file filters to select from for applying to the files inside the current directory. + /// Strings according to the original .NET API will be converted automatically + /// (see https://docs.microsoft.com/de-de/dotnet/api/microsoft.win32.filedialog.filter?view=netframework-4.7.1#Microsoft_Win32_FileDialog_Filter). + /// + public string Filters { get; set; } + + /// + /// The index of the file filter to apply to the files inside the current directory. + /// + public int FilterIndex { get; set; } + + /// + /// Creates a new . + /// + public OpenMultipleFilesDialogArguments() + : base() + { + Filters = null; + FilterIndex = 0; + } + } + + /// + /// The dialog result for . + /// + public class OpenMultipleFilesDialogResult : FileSystemDialogResult + { + + /// + /// The selected files as . + /// + public List FileInfoList { get; private set; } + + /// + /// The selected files as full filename string. + /// + public List Files + { + get + { + return FileInfoList + .Select(fileInfo => fileInfo.FullName) + .ToList(); + } + } + + /// + /// Creates a new . + /// + /// True if the dialog was canceled + /// The list of selected files + public OpenMultipleFilesDialogResult(bool canceled, List fileInfoList) + : base(canceled) + { + if (fileInfoList != null) + { + FileInfoList = fileInfoList + .OrderBy(fileInfo => fileInfo.FullName.ToLower()) + .ToList(); + } + } + } +} diff --git a/MaterialDesignExtensions/Controls/OversizedNumberSpinner.cs b/MaterialDesignExtensions/Controls/OversizedNumberSpinner.cs index c6c9dabf..2bbe03e5 100644 --- a/MaterialDesignExtensions/Controls/OversizedNumberSpinner.cs +++ b/MaterialDesignExtensions/Controls/OversizedNumberSpinner.cs @@ -7,6 +7,8 @@ using System.Windows.Controls; using System.Windows.Input; +using MaterialDesignExtensions.Commands.Internal; + namespace MaterialDesignExtensions.Controls { /// @@ -17,23 +19,14 @@ public class OversizedNumberSpinner : Control private const string ValueTextBoxName = "ValueTextBox"; /// - /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. - /// - public static readonly RoutedCommand EditValueCommand = new RoutedCommand(); - - /// - /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. - /// - public static readonly RoutedCommand MinusCommand = new RoutedCommand(); - - /// - /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. + /// True, if the editing mode with the textbox is active. /// - public static readonly RoutedCommand PlusCommand = new RoutedCommand(); - public static readonly DependencyProperty IsEditingProperty = DependencyProperty.Register( nameof(IsEditing), typeof(bool), typeof(OversizedNumberSpinner), new PropertyMetadata(false)); + /// + /// True, if the editing mode with the textbox is active. + /// private bool IsEditing { get @@ -47,9 +40,15 @@ private bool IsEditing } } + /// + /// The minimum value of the . + /// public static readonly DependencyProperty MinProperty = DependencyProperty.Register( nameof(Min), typeof(int), typeof(OversizedNumberSpinner), new PropertyMetadata(0)); + /// + /// The minimum value of the . + /// public int Min { get @@ -63,9 +62,15 @@ public int Min } } + /// + /// The maximum value of the . + /// public static readonly DependencyProperty MaxProperty = DependencyProperty.Register( nameof(Max), typeof(int), typeof(OversizedNumberSpinner), new PropertyMetadata(5)); + /// + /// The maximum value of the . + /// public int Max { get @@ -79,9 +84,15 @@ public int Max } } + /// + /// The current value of the . + /// public static readonly DependencyProperty ValueProperty = DependencyProperty.Register( nameof(Value), typeof(int), typeof(OversizedNumberSpinner), new PropertyMetadata(1, ValuePropertyChangedCallback)); + /// + /// The current value of the . + /// public int Value { get @@ -102,12 +113,15 @@ static OversizedNumberSpinner() DefaultStyleKeyProperty.OverrideMetadata(typeof(OversizedNumberSpinner), new FrameworkPropertyMetadata(typeof(OversizedNumberSpinner))); } + /// + /// Creates a new . + /// public OversizedNumberSpinner() : base() { - CommandBindings.Add(new CommandBinding(EditValueCommand, EditValueCommandHandler)); - CommandBindings.Add(new CommandBinding(MinusCommand, MinusCommandHandler)); - CommandBindings.Add(new CommandBinding(PlusCommand, PlusCommandHandler)); + CommandBindings.Add(new CommandBinding(OversizedNumberSpinnerCommands.EditValueCommand, EditValueCommandHandler)); + CommandBindings.Add(new CommandBinding(OversizedNumberSpinnerCommands.MinusCommand, MinusCommandHandler)); + CommandBindings.Add(new CommandBinding(OversizedNumberSpinnerCommands.PlusCommand, PlusCommandHandler)); Loaded += LoadedHandler; Unloaded += UnloadedHandler; @@ -128,20 +142,35 @@ public override void OnApplyTemplate() private void LoadedHandler(object sender, RoutedEventArgs args) { - m_valueTextBox.LostFocus += LostFocusHandler; - m_valueTextBox.KeyUp += KeyUpHandler; + if (m_valueTextBox != null) + { + m_valueTextBox.LostFocus += LostFocusHandler; + m_valueTextBox.KeyUp += KeyUpHandler; + } } private void UnloadedHandler(object sender, RoutedEventArgs args) { - m_valueTextBox.LostFocus -= LostFocusHandler; - m_valueTextBox.KeyUp -= KeyUpHandler; + if (m_valueTextBox != null) + { + m_valueTextBox.LostFocus -= LostFocusHandler; + m_valueTextBox.KeyUp -= KeyUpHandler; + } } private void EditValueCommandHandler(object sender, ExecutedRoutedEventArgs args) { IsEditing = true; - m_valueTextBox.Focus(); + + try + { + m_valueTextBox.Focus(); + } + catch (InvalidOperationException) + { + // This is a hack. The above call of Focus() will cause an exception inside MaterialDesignThemes version 2.5.0.1205. + // Older or newer versions of MaterialDesignThemes work as expected. + } } private void MinusCommandHandler(object sender, ExecutedRoutedEventArgs args) diff --git a/MaterialDesignExtensions/Controls/PersistentSearch.cs b/MaterialDesignExtensions/Controls/PersistentSearch.cs index 7fd44fcd..595074fe 100644 --- a/MaterialDesignExtensions/Controls/PersistentSearch.cs +++ b/MaterialDesignExtensions/Controls/PersistentSearch.cs @@ -13,6 +13,28 @@ namespace MaterialDesignExtensions.Controls /// public class PersistentSearch : SearchBase { + /// + /// The size of the displayed icons. + /// + public static readonly DependencyProperty IconSizeProperty = DependencyProperty.Register( + nameof(IconSize), typeof(int), typeof(PersistentSearch), new PropertyMetadata(24)); + + /// + /// The size of the displayed icons. + /// + public string IconSize + { + get + { + return (string)GetValue(IconSizeProperty); + } + + set + { + SetValue(IconSizeProperty, value); + } + } + static PersistentSearch() { DefaultStyleKeyProperty.OverrideMetadata(typeof(PersistentSearch), new FrameworkPropertyMetadata(typeof(PersistentSearch))); diff --git a/MaterialDesignExtensions/Controls/SaveFileControl.cs b/MaterialDesignExtensions/Controls/SaveFileControl.cs index 8812487e..7a2fe34c 100644 --- a/MaterialDesignExtensions/Controls/SaveFileControl.cs +++ b/MaterialDesignExtensions/Controls/SaveFileControl.cs @@ -11,6 +11,13 @@ using MaterialDesignExtensions.Controllers; +// use Pri.LongPath classes instead of System.IO for the MaterialDesignExtensions.LongPath build to support long file system paths on older Windows and .NET versions +#if LONG_PATH +using DirectoryInfo = Pri.LongPath.DirectoryInfo; +using FileInfo = Pri.LongPath.FileInfo; +using FileSystemInfo = Pri.LongPath.FileSystemInfo; +#endif + namespace MaterialDesignExtensions.Controls { /// @@ -18,6 +25,8 @@ namespace MaterialDesignExtensions.Controls /// public class SaveFileControl : BaseFileControl { + private const string FilenameTextBoxName = "filenameTextBox"; + /// /// The name of the file itself without the full path. /// @@ -43,6 +52,33 @@ public string Filename } } + /// + /// Forces the possible file extension of the selected file filter for new filenames. + /// + public static readonly DependencyProperty ForceFileExtensionOfFileFilterProperty = DependencyProperty.Register( + nameof(ForceFileExtensionOfFileFilter), + typeof(bool), + typeof(SaveFileControl), + new PropertyMetadata(false, ForceFileExtensionOfFileFilterChangedHandler)); + + /// + /// Forces the possible file extension of the selected file filter for new filenames. + /// + public bool ForceFileExtensionOfFileFilter + { + get + { + return (bool)GetValue(ForceFileExtensionOfFileFilterProperty); + } + + set + { + SetValue(ForceFileExtensionOfFileFilterProperty, value); + } + } + + private TextBox m_filenameTextBox; + static SaveFileControl() { DefaultStyleKeyProperty.OverrideMetadata(typeof(SaveFileControl), new FrameworkPropertyMetadata(typeof(SaveFileControl))); @@ -51,7 +87,46 @@ static SaveFileControl() /// /// Creates a new . /// - public SaveFileControl() : base() { } + public SaveFileControl() + : base() + { + m_filenameTextBox = null; + } + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + if (m_filenameTextBox != null) + { + m_filenameTextBox.KeyUp -= FilenameTextBoxKeyUpHandler; + } + + m_filenameTextBox = Template.FindName(FilenameTextBoxName, this) as TextBox; + } + + protected override void LoadedHandler(object sender, RoutedEventArgs args) + { + base.LoadedHandler(sender, args); + + m_filenameTextBox.KeyUp -= FilenameTextBoxKeyUpHandler; + m_filenameTextBox.KeyUp += FilenameTextBoxKeyUpHandler; + } + + protected override void UnloadedHandler(object sender, RoutedEventArgs args) + { + m_filenameTextBox.KeyUp -= FilenameTextBoxKeyUpHandler; + + base.UnloadedHandler(sender, args); + } + + private void FilenameTextBoxKeyUpHandler(object sender, KeyEventArgs args) + { + if (args.Key == Key.Enter && !string.IsNullOrWhiteSpace(CurrentFile)) + { + SelectFile(); + } + } protected override void SelectFileCommandHandler(object sender, ExecutedRoutedEventArgs args) { @@ -82,7 +157,7 @@ protected virtual void FilenameChangedHandler(string newFilename) { try { - m_controller.SelectFile(BuildFullFilename(newFilename)); + m_controller.SelectFile(m_controller.BuildFullFileNameForInCurrentDirectory(newFilename)); } catch (PathTooLongException) { @@ -122,47 +197,36 @@ protected override void ControllerPropertyChangedHandler(object sender, Property base.ControllerPropertyChangedHandler(sender, args); } - protected override void SelectFileSystemEntryCommandHandler(object sender, ExecutedRoutedEventArgs args) + protected override void SelectFileSystemEntry(FileSystemInfo fileSystemInfo) { - if (args.Parameter != null) + if (fileSystemInfo != null) { - if (args.Parameter is DirectoryInfo directoryInfo) + if (fileSystemInfo is DirectoryInfo directoryInfo) { CurrentDirectory = directoryInfo.FullName; if (Filename != null && CurrentDirectory != null) { - CurrentFile = BuildFullFilename(Filename); + CurrentFile = m_controller.BuildFullFileNameForInCurrentDirectory(Filename); } else { CurrentFile = null; } } - else if (args.Parameter is FileInfo fileInfo) + else if (fileSystemInfo is FileInfo fileInfo) { CurrentFile = fileInfo.FullName; } } } - private string BuildFullFilename(string newFilename) + protected static void ForceFileExtensionOfFileFilterChangedHandler(DependencyObject obj, DependencyPropertyChangedEventArgs args) { - string filename = null; - - if (!string.IsNullOrWhiteSpace(newFilename)) + if (obj is SaveFileControl saveFileControl) { - string directory = CurrentDirectory; - - if (CurrentDirectory != null && !directory.EndsWith(@"\") && !directory.EndsWith("/")) - { - directory = directory + @"\"; - } - - filename = directory + newFilename.Trim(); + saveFileControl.m_controller.ForceFileExtensionOfFileFilter = (bool)args.NewValue; } - - return filename; } } } diff --git a/MaterialDesignExtensions/Controls/SaveFileDialog.cs b/MaterialDesignExtensions/Controls/SaveFileDialog.cs index 1033a07c..cffb0bcc 100644 --- a/MaterialDesignExtensions/Controls/SaveFileDialog.cs +++ b/MaterialDesignExtensions/Controls/SaveFileDialog.cs @@ -11,6 +11,11 @@ using MaterialDesignExtensions.Converters; using MaterialDesignExtensions.Model; +// use Pri.LongPath classes instead of System.IO for the MaterialDesignExtensions.LongPath build to support long file system paths on older Windows and .NET versions +#if LONG_PATH +using FileInfo = Pri.LongPath.FileInfo; +#endif + namespace MaterialDesignExtensions.Controls { /// @@ -43,6 +48,31 @@ public string Filename } } + /// + /// Forces the possible file extension of the selected file filter for new filenames. + /// + public static readonly DependencyProperty ForceFileExtensionOfFileFilterProperty = DependencyProperty.Register( + nameof(ForceFileExtensionOfFileFilter), + typeof(bool), + typeof(SaveFileDialog), + new PropertyMetadata(false)); + + /// + /// Forces the possible file extension of the selected file filter for new filenames. + /// + public bool ForceFileExtensionOfFileFilter + { + get + { + return (bool)GetValue(ForceFileExtensionOfFileFilterProperty); + } + + set + { + SetValue(ForceFileExtensionOfFileFilterProperty, value); + } + } + static SaveFileDialog() { DefaultStyleKeyProperty.OverrideMetadata(typeof(SaveFileDialog), new FrameworkPropertyMetadata(typeof(SaveFileDialog))); @@ -63,30 +93,6 @@ protected override void OpenDirectoryControlFileSelectedHandler(object sender, R DialogHost.CloseDialogCommand.Execute(new SaveFileDialogResult(false, (args as FileSelectedEventArgs)?.FileInfo), GetDialogHost()); } - /// - /// Shows a new . - /// - /// The name of the - /// The width of the dialog (optional) - /// The heigth of the dialog (optional) - /// The current directory to show (optional) - /// The name of the file without the full path (optional) - /// Show or hide hidden files in the dialog (optional) - /// Show or hide system files in the dialog (optional) - /// Callback after openening the dialog (optional) - /// Callback after closing the dialog (optional) - /// - [Obsolete("Use the overloaded method with SaveFileDialogArguments instead")] - public static async Task ShowDialogAsync(string dialogHostName, double? width = null, double? height = null, - string currentDirectory = null, string filename = null, - bool showHiddenFilesAndDirectories = false, bool showSystemFilesAndDirectories = false, - DialogOpenedEventHandler openedHandler = null, DialogClosingEventHandler closingHandler = null) - { - SaveFileDialog dialog = InitDialog(width, height, currentDirectory, filename, null, -1, showHiddenFilesAndDirectories, showSystemFilesAndDirectories); - - return await DialogHost.Show(dialog, dialogHostName, openedHandler, closingHandler) as SaveFileDialogResult; - } - /// /// Shows a new . /// @@ -102,37 +108,17 @@ public static async Task ShowDialogAsync(string dialogHost args.Filename, args.Filters, args.FilterIndex, + args.CreateNewDirectoryEnabled, args.ShowHiddenFilesAndDirectories, - args.ShowSystemFilesAndDirectories + args.ShowSystemFilesAndDirectories, + args.SwitchPathPartsAsButtonsEnabled, + args.PathPartsAsButtons, + args.ForceFileExtensionOfFileFilter ); return await DialogHost.Show(dialog, dialogHostName, args.OpenedHandler, args.ClosingHandler) as SaveFileDialogResult; } - /// - /// Shows a new . - /// - /// The - /// The width of the dialog (optional) - /// The heigth of the dialog (optional) - /// The current directory to show (optional) - /// The name of the file without the full path (optional) - /// Show or hide hidden files in the dialog (optional) - /// Show or hide system files in the dialog (optional) - /// Callback after openening the dialog (optional) - /// Callback after closing the dialog (optional) - /// - [Obsolete("Use the overloaded method with SaveFileDialogArguments instead")] - public static async Task ShowDialogAsync(DialogHost dialogHost, double? width = null, double? height = null, - string currentDirectory = null, string filename = null, - bool showHiddenFilesAndDirectories = false, bool showSystemFilesAndDirectories = false, - DialogOpenedEventHandler openedHandler = null, DialogClosingEventHandler closingHandler = null) - { - SaveFileDialog dialog = InitDialog(width, height, currentDirectory, filename, null, -1, showHiddenFilesAndDirectories, showSystemFilesAndDirectories); - - return await dialogHost.ShowDialog(dialog, openedHandler, closingHandler) as SaveFileDialogResult; - } - /// /// Shows a new . /// @@ -148,8 +134,12 @@ public static async Task ShowDialogAsync(DialogHost dialog args.Filename, args.Filters, args.FilterIndex, + args.CreateNewDirectoryEnabled, args.ShowHiddenFilesAndDirectories, - args.ShowSystemFilesAndDirectories + args.ShowSystemFilesAndDirectories, + args.SwitchPathPartsAsButtonsEnabled, + args.PathPartsAsButtons, + args.ForceFileExtensionOfFileFilter ); return await dialogHost.ShowDialog(dialog, args.OpenedHandler, args.ClosingHandler) as SaveFileDialogResult; @@ -158,11 +148,15 @@ public static async Task ShowDialogAsync(DialogHost dialog private static SaveFileDialog InitDialog(double? width, double? height, string currentDirectory, string filename, string filters, int filterIndex, - bool showHiddenFilesAndDirectories, bool showSystemFilesAndDirectories) + bool createNewDirectoryEnabled, + bool showHiddenFilesAndDirectories, bool showSystemFilesAndDirectories, + bool switchPathPartsAsButtonsEnabled, bool pathPartsAsButtons, + bool forceFileExtensionOfFileFilter) { SaveFileDialog dialog = new SaveFileDialog(); - InitDialog(dialog, width, height, currentDirectory, showHiddenFilesAndDirectories, showSystemFilesAndDirectories); + InitDialog(dialog, width, height, currentDirectory, showHiddenFilesAndDirectories, showSystemFilesAndDirectories, createNewDirectoryEnabled, switchPathPartsAsButtonsEnabled, pathPartsAsButtons); dialog.Filename = filename; + dialog.ForceFileExtensionOfFileFilter = forceFileExtensionOfFileFilter; dialog.Filters = new FileFiltersTypeConverter().ConvertFrom(null, null, filters) as IList; dialog.FilterIndex = filterIndex; @@ -180,10 +174,22 @@ public class SaveFileDialogArguments : FileDialogArguments /// public string Filename { get; set; } + /// + /// Forces the possible file extension of the selected file filter for new filenames. + /// + public bool ForceFileExtensionOfFileFilter { get; set; } + /// /// Creates a new . /// public SaveFileDialogArguments() : base() { } + + /// + /// Copy constructor + /// + /// + public SaveFileDialogArguments(SaveFileDialogArguments args) + : base(args) { } } /// diff --git a/MaterialDesignExtensions/Controls/SearchBase.cs b/MaterialDesignExtensions/Controls/SearchBase.cs index 830c1534..188b14e0 100644 --- a/MaterialDesignExtensions/Controls/SearchBase.cs +++ b/MaterialDesignExtensions/Controls/SearchBase.cs @@ -10,6 +10,7 @@ using MaterialDesignThemes.Wpf; +using MaterialDesignExtensions.Commands.Internal; using MaterialDesignExtensions.Controllers; using MaterialDesignExtensions.Model; @@ -26,11 +27,6 @@ public abstract class SearchBase : ControlWithAutocompletePopup protected const string SearchSuggestionsPopupName = "searchSuggestionsPopup"; protected const string SearchSuggestionsItemsControlName = "searchSuggestionsItemsControl"; - /// - /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. - /// - public static readonly RoutedCommand SelectSearchSuggestionCommand = new RoutedCommand(); - /// /// An event raised by triggering a search (select a suggestion or hit enter). /// @@ -119,6 +115,50 @@ public object SearchIcon } } + /// + /// The icon on the left side of the search text box that appears when the text is not empty and replaced the search icon. + /// + public static readonly DependencyProperty CancelIconProperty = DependencyProperty.Register( + nameof(CancelIcon), typeof(object), typeof(SearchBase), new PropertyMetadata(PackIconKind.ArrowLeft, null)); + + /// + /// The icon on the left side of the search text box that appears when the text is not empty and replaced the search icon. + /// + public object CancelIcon + { + get + { + return GetValue(CancelIconProperty); + } + + set + { + SetValue(CancelIconProperty, value); + } + } + + /// + /// The icon on the right side of the search text box that appears when the text is not empty. + /// + public static readonly DependencyProperty ClearIconProperty = DependencyProperty.Register( + nameof(ClearIcon), typeof(object), typeof(SearchBase), new PropertyMetadata(PackIconKind.Close, null)); + + /// + /// The icon on the right side of the search text box that appears when the text is not empty. + /// + public object ClearIcon + { + get + { + return GetValue(ClearIconProperty); + } + + set + { + SetValue(ClearIconProperty, value); + } + } + /// /// A source for providing suggestions to search for. /// @@ -170,6 +210,9 @@ public string SearchTerm private SearchSuggestionsController m_searchSuggestionsController; + /// + /// Creates a new . + /// public SearchBase() : base() { @@ -179,7 +222,7 @@ public SearchBase() m_searchSuggestionsController = new SearchSuggestionsController() { SearchSuggestionsSource = SearchSuggestionsSource }; - CommandBindings.Add(new CommandBinding(SelectSearchSuggestionCommand, SelectSearchSuggestionCommandHandler)); + CommandBindings.Add(new CommandBinding(SearchControlCommands.SelectSearchSuggestionCommand, SelectSearchSuggestionCommandHandler)); Loaded += LoadedHandler; Unloaded += UnloadedHandler; diff --git a/MaterialDesignExtensions/Controls/SideNavigation.cs b/MaterialDesignExtensions/Controls/SideNavigation.cs index 442eedd7..bd2dd1b3 100644 --- a/MaterialDesignExtensions/Controls/SideNavigation.cs +++ b/MaterialDesignExtensions/Controls/SideNavigation.cs @@ -11,22 +11,18 @@ using System.Windows.Input; using System.Windows.Media; +using MaterialDesignExtensions.Commands.Internal; using MaterialDesignExtensions.Model; namespace MaterialDesignExtensions.Controls { /// - /// A control to be used as side navigation or inside a navigation drawer. + /// A navigation control to be used as side navigation or inside a navigation drawer. /// public class SideNavigation : Control { private const string NavigationItemsControlName = "navigationItemsControl"; - /// - /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. - /// - public static readonly RoutedCommand SelectNavigationItemCommand = new RoutedCommand(); - /// /// An event raised by selecting an item. /// @@ -71,6 +67,72 @@ public event WillSelectNavigationItemEventHandler WillSelectNavigationItem } } + /// + /// The foreground color of the icon of not selected items. + /// + public static readonly DependencyProperty IconForegroundProperty = DependencyProperty.Register( + nameof(IconForeground), typeof(Brush), typeof(SideNavigation), new PropertyMetadata(null, null)); + + /// + /// The foreground color of the icon of not selected items. + /// + public Brush IconForeground + { + get + { + return (Brush)GetValue(IconForegroundProperty); + } + + set + { + SetValue(IconForegroundProperty, value); + } + } + + /// + /// The font size of the item labels. + /// + public static readonly DependencyProperty LabelFontSizeProperty = DependencyProperty.Register( + nameof(LabelFontSize), typeof(double), typeof(SideNavigation), new PropertyMetadata(14.0, null)); + + /// + /// The font size of the item labels. + /// + public double LabelFontSize + { + get + { + return (double)GetValue(LabelFontSizeProperty); + } + + set + { + SetValue(LabelFontSizeProperty, value); + } + } + + /// + /// The foreground color of the label of not selected items. + /// + public static readonly DependencyProperty LabelForegroundProperty = DependencyProperty.Register( + nameof(LabelForeground), typeof(Brush), typeof(SideNavigation), new PropertyMetadata(null, null)); + + /// + /// The foreground color of the label of not selected items. + /// + public Brush LabelForeground + { + get + { + return (Brush)GetValue(LabelForegroundProperty); + } + + set + { + SetValue(LabelForegroundProperty, value); + } + } + /// /// The color of the click ripple by selecting an item. /// @@ -94,13 +156,13 @@ public Brush NavigationItemFeedback } /// - /// The background color of the selected item. It will get an opacity of 15%. + /// The background color of the selected item. /// public static readonly DependencyProperty SelectionBackgroundProperty = DependencyProperty.Register( nameof(SelectionBackground), typeof(Brush), typeof(SideNavigation), new PropertyMetadata(null, null)); /// - /// The background color of the selected item. It will get an opacity of 15%. + /// The background color of the selected item. /// public Brush SelectionBackground { @@ -115,6 +177,28 @@ public Brush SelectionBackground } } + /// + /// The background color opacity of the selected item. + /// + public static readonly DependencyProperty SelectionBackgroundOpacityProperty = DependencyProperty.Register( + nameof(SelectionBackgroundOpacity), typeof(double), typeof(SideNavigation), new PropertyMetadata(0.12, null)); + + /// + /// The background color opacity of the selected item. + /// + public double SelectionBackgroundOpacity + { + get + { + return (double)GetValue(SelectionBackgroundOpacityProperty); + } + + set + { + SetValue(SelectionBackgroundOpacityProperty, value); + } + } + /// /// The corner radius of the selected item. /// @@ -280,7 +364,7 @@ public ICommand WillSelectNavigationItemCommand } } - private ItemsControl m_navigationItemsControl; + protected ItemsControl m_navigationItemsControl; static SideNavigation() { @@ -296,7 +380,7 @@ public SideNavigation() Loaded += LoadedHandler; Unloaded += UnloadedHandler; - CommandBindings.Add(new CommandBinding(SelectNavigationItemCommand, SelectNavigationItemCommandHandlerAsync)); + CommandBindings.Add(new CommandBinding(SideNavigationCommands.SelectNavigationItemCommand, SelectNavigationItemCommandHandlerAsync)); } public override void OnApplyTemplate() @@ -392,26 +476,38 @@ private async void SelectNavigationItemCommandHandlerAsync(object sender, Execut } } - private void InitItems(IList values) + protected virtual void InitItems(IList values) { - IList navigationItems = new List(); - - if (values != null) + if (m_navigationItemsControl != null) { - foreach (object item in values) + IList navigationItems = new List(); + + if (values != null) { - if (item is INavigationItem navigationItem) + foreach (object item in values) { - navigationItems.Add(navigationItem); + if (item is INavigationItem navigationItem) + { + navigationItems.Add(navigationItem); + } } } - } - m_navigationItemsControl.ItemsSource = navigationItems; + m_navigationItemsControl.ItemsSource = navigationItems; - if (SelectedItem != null && !navigationItems.Contains(SelectedItem)) - { - SelectedItem = null; + INavigationItem selectedItem = navigationItems.FirstOrDefault(item => item.IsSelected); + + if (selectedItem != null) + { + if (SelectedItem != selectedItem) + { + SelectedItem = selectedItem; + } + } + else + { + SelectedItem = null; + } } } } diff --git a/MaterialDesignExtensions/Controls/StepButtonBar.cs b/MaterialDesignExtensions/Controls/StepButtonBar.cs index 65f568b9..10709510 100644 --- a/MaterialDesignExtensions/Controls/StepButtonBar.cs +++ b/MaterialDesignExtensions/Controls/StepButtonBar.cs @@ -8,6 +8,8 @@ using System.Windows.Input; using System.Windows.Media; +using MaterialDesignExtensions.Commands.Internal; + namespace MaterialDesignExtensions.Controls { /// @@ -37,6 +39,7 @@ public object Back SetValue(BackProperty, value); } } + /// /// The interal back command of the parent stepper. /// @@ -80,6 +83,7 @@ public object Cancel SetValue(CancelProperty, value); } } + /// /// The interal cancel command of the parent stepper. /// @@ -239,6 +243,9 @@ static StepButtonBar() DefaultStyleKeyProperty.OverrideMetadata(typeof(StepButtonBar), new FrameworkPropertyMetadata(typeof(StepButtonBar))); } + /// + /// Creates a new . + /// public StepButtonBar() : base() { } public override void OnApplyTemplate() @@ -250,18 +257,9 @@ public override void OnApplyTemplate() if (stepper != null) { - if (stepper is TabControlStepper) - { - BackCommand = TabControlStepper.BackCommand; - CancelCommand = TabControlStepper.CancelCommand; - ContinueCommand = TabControlStepper.ContinueCommand; - } - else - { - BackCommand = Stepper.BackCommand; - CancelCommand = Stepper.CancelCommand; - ContinueCommand = Stepper.ContinueCommand; - } + BackCommand = StepperCommands.BackCommand; + CancelCommand = StepperCommands.CancelCommand; + ContinueCommand = StepperCommands.ContinueCommand; } base.OnApplyTemplate(); diff --git a/MaterialDesignExtensions/Controls/StepTitleHeaderControl.cs b/MaterialDesignExtensions/Controls/StepTitleHeaderControl.cs index 5d4a631a..0ac7bc85 100644 --- a/MaterialDesignExtensions/Controls/StepTitleHeaderControl.cs +++ b/MaterialDesignExtensions/Controls/StepTitleHeaderControl.cs @@ -8,6 +8,9 @@ namespace MaterialDesignExtensions.Controls { + /// + /// Control for a header of a stepper step to enable data bindings. + /// public class StepTitleHeaderControl : Control { /// diff --git a/MaterialDesignExtensions/Controls/Stepper.cs b/MaterialDesignExtensions/Controls/Stepper.cs index aaa69733..20a6545b 100644 --- a/MaterialDesignExtensions/Controls/Stepper.cs +++ b/MaterialDesignExtensions/Controls/Stepper.cs @@ -13,6 +13,7 @@ using System.Windows.Markup; using System.Windows.Media.Animation; +using MaterialDesignExtensions.Commands.Internal; using MaterialDesignExtensions.Controllers; using MaterialDesignExtensions.Model; @@ -24,26 +25,6 @@ namespace MaterialDesignExtensions.Controls [ContentProperty(nameof(Steps))] public class Stepper : Control, IStepper { - /// - /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. - /// - public static readonly RoutedCommand BackCommand = new RoutedCommand(); - - /// - /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. - /// - public static readonly RoutedCommand CancelCommand = new RoutedCommand(); - - /// - /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. - /// - public static readonly RoutedCommand ContinueCommand = new RoutedCommand(); - - /// - /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. - /// - public static readonly RoutedCommand StepSelectedCommand = new RoutedCommand(); - /// /// An event raised by changing the active . /// @@ -354,6 +335,28 @@ public ICommand ContinueNavigationCommand } } + /// + /// An alternative icon template done steps. + /// + public static readonly DependencyProperty DoneIconTemplateProperty = DependencyProperty.Register( + nameof(DoneIconTemplate), typeof(DataTemplate), typeof(Stepper), new PropertyMetadata(null, null)); + + /// + /// An alternative icon template done steps. + /// + public DataTemplate DoneIconTemplate + { + get + { + return (DataTemplate)GetValue(DoneIconTemplateProperty); + } + + set + { + SetValue(DoneIconTemplateProperty, value); + } + } + /// /// Enables the linear mode by disabling the buttons of the header. /// The navigation must be accomplished by using the navigation commands. @@ -489,17 +492,31 @@ public ICommand StepValidationCommand } /// - /// Gets the controller for this . + /// An alternative icon template to indicate validation errors. + /// + public static readonly DependencyProperty ValidationErrorIconTemplateProperty = DependencyProperty.Register( + nameof(ValidationErrorIconTemplate), typeof(DataTemplate), typeof(Stepper), new PropertyMetadata(null, null)); + + /// + /// An alternative icon template to indicate validation errors. /// - public StepperController Controller + public DataTemplate ValidationErrorIconTemplate { get { - return m_controller; + return (DataTemplate)GetValue(ValidationErrorIconTemplateProperty); + } + + set + { + SetValue(ValidationErrorIconTemplateProperty, value); } } - private StepperController m_controller; + /// + /// Gets the controller for this . + /// + public StepperController Controller { get; private set; } static Stepper() { @@ -512,24 +529,24 @@ static Stepper() public Stepper() : base() { - m_controller = new StepperController(); + Controller = new StepperController(); Steps = new ObservableCollection(); Loaded += LoadedHandler; Unloaded += UnloadedHandler; - CommandBindings.Add(new CommandBinding(BackCommand, BackHandler)); - CommandBindings.Add(new CommandBinding(CancelCommand, CancelHandler)); - CommandBindings.Add(new CommandBinding(ContinueCommand, ContinueHandler)); - CommandBindings.Add(new CommandBinding(StepSelectedCommand, StepSelectedHandler, CanExecuteStepSelectedHandler)); + CommandBindings.Add(new CommandBinding(StepperCommands.BackCommand, BackHandler)); + CommandBindings.Add(new CommandBinding(StepperCommands.CancelCommand, CancelHandler)); + CommandBindings.Add(new CommandBinding(StepperCommands.ContinueCommand, ContinueHandler)); + CommandBindings.Add(new CommandBinding(StepperCommands.StepSelectedCommand, StepSelectedHandler, CanExecuteStepSelectedHandler)); Focusable = false; } private void LoadedHandler(object sender, RoutedEventArgs args) { - m_controller.PropertyChanged += PropertyChangedHandler; + Controller.PropertyChanged += PropertyChangedHandler; if (Steps is ObservableCollection steps) { @@ -546,7 +563,7 @@ private void LoadedHandler(object sender, RoutedEventArgs args) private void UnloadedHandler(object sender, RoutedEventArgs args) { - m_controller.PropertyChanged -= PropertyChangedHandler; + Controller.PropertyChanged -= PropertyChangedHandler; if (Steps is ObservableCollection steps) { @@ -556,7 +573,7 @@ private void UnloadedHandler(object sender, RoutedEventArgs args) private bool ValidateActiveStep() { - IStep step = m_controller.ActiveStepViewModel?.Step; + IStep step = Controller.ActiveStepViewModel?.Step; if (step != null) { @@ -584,7 +601,7 @@ private bool ValidateActiveStep() private void RaiseNavigationCanceledByValidation() { - IStep step = m_controller.ActiveStepViewModel?.Step; + IStep step = Controller.ActiveStepViewModel?.Step; if (step != null) { @@ -600,18 +617,22 @@ private void RaiseNavigationCanceledByValidation() private void SelectStep(IStep step) { - if (step != null && step != m_controller.ActiveStep && !IsLinear) + if (step != null && step != Controller.ActiveStep && !IsLinear) { bool isValid = ValidateActiveStep(); - if (BlockNavigationOnValidationErrors && !isValid) + StepperStepViewModel activeStepViewModel = Controller.ActiveStepViewModel; + StepperStepViewModel nextStepViewModel = Controller.InternalSteps.FirstOrDefault(stepViewModel => stepViewModel.Step == step); + + if (BlockNavigationOnValidationErrors && !isValid + && (nextStepViewModel == null || nextStepViewModel.Number > activeStepViewModel.Number)) { RaiseNavigationCanceledByValidation(); return; } - StepperNavigationEventArgs navigationArgs = new StepperNavigationEventArgs(StepNavigationEvent, this, m_controller.ActiveStep, step, false); + StepperNavigationEventArgs navigationArgs = new StepperNavigationEventArgs(StepNavigationEvent, this, Controller.ActiveStep, step, false); RaiseEvent(navigationArgs); if (StepNavigationCommand != null && StepNavigationCommand.CanExecute(navigationArgs)) @@ -621,12 +642,12 @@ private void SelectStep(IStep step) if (!navigationArgs.Cancel) { - m_controller.GotoStep(step); + Controller.GotoStep(step); } else { // refresh the property with the old state - ActiveStep = m_controller.ActiveStep; + ActiveStep = Controller.ActiveStep; } } } @@ -681,21 +702,14 @@ private void InitSteps(IList values) } } - m_controller.InitSteps(steps); + Controller.InitSteps(steps); } private void BackHandler(object sender, ExecutedRoutedEventArgs args) { - bool isValid = ValidateActiveStep(); - - if (BlockNavigationOnValidationErrors && !isValid) - { - RaiseNavigationCanceledByValidation(); - - return; - } + ValidateActiveStep(); - StepperNavigationEventArgs navigationArgs = new StepperNavigationEventArgs(BackNavigationEvent, this, m_controller.ActiveStep, m_controller.PreviousStep, false); + StepperNavigationEventArgs navigationArgs = new StepperNavigationEventArgs(BackNavigationEvent, this, Controller.ActiveStep, Controller.PreviousStep, false); RaiseEvent(navigationArgs); if (BackNavigationCommand != null && BackNavigationCommand.CanExecute(navigationArgs)) @@ -705,7 +719,7 @@ private void BackHandler(object sender, ExecutedRoutedEventArgs args) if (!navigationArgs.Cancel) { - m_controller.Back(); + Controller.Back(); } } @@ -716,7 +730,7 @@ private void CancelHandler(object sender, ExecutedRoutedEventArgs args) return; } - StepperNavigationEventArgs navigationArgs = new StepperNavigationEventArgs(CancelNavigationEvent, this, m_controller.ActiveStep, null, false); + StepperNavigationEventArgs navigationArgs = new StepperNavigationEventArgs(CancelNavigationEvent, this, Controller.ActiveStep, null, false); RaiseEvent(navigationArgs); if (CancelNavigationCommand != null && CancelNavigationCommand.CanExecute(navigationArgs)) @@ -736,7 +750,7 @@ private void ContinueHandler(object sender, ExecutedRoutedEventArgs args) return; } - StepperNavigationEventArgs navigationArgs = new StepperNavigationEventArgs(ContinueNavigationEvent, this, m_controller.ActiveStep, m_controller.NextStep, false); + StepperNavigationEventArgs navigationArgs = new StepperNavigationEventArgs(ContinueNavigationEvent, this, Controller.ActiveStep, Controller.NextStep, false); RaiseEvent(navigationArgs); if (ContinueNavigationCommand != null && ContinueNavigationCommand.CanExecute(navigationArgs)) @@ -746,7 +760,7 @@ private void ContinueHandler(object sender, ExecutedRoutedEventArgs args) if (!navigationArgs.Cancel) { - m_controller.Continue(); + Controller.Continue(); } } @@ -762,15 +776,15 @@ private void StepSelectedHandler(object sender, ExecutedRoutedEventArgs args) private void PropertyChangedHandler(object sender, PropertyChangedEventArgs args) { - if (sender == m_controller) + if (sender == Controller) { - if (args.PropertyName == nameof(m_controller.ActiveStep)) + if (args.PropertyName == nameof(Controller.ActiveStep)) { // set the property - ActiveStep = m_controller.ActiveStep; + ActiveStep = Controller.ActiveStep; // raise the event and call the command - ActiveStepChangedEventArgs eventArgs = new ActiveStepChangedEventArgs(StepValidationEvent, this, ActiveStep); + ActiveStepChangedEventArgs eventArgs = new ActiveStepChangedEventArgs(ActiveStepChangedEvent, this, ActiveStep); RaiseEvent(eventArgs); if (ActiveStepChangedCommand != null && ActiveStepChangedCommand.CanExecute(ActiveStep)) @@ -778,8 +792,8 @@ private void PropertyChangedHandler(object sender, PropertyChangedEventArgs args ActiveStepChangedCommand.Execute(ActiveStep); } } - else if (args.PropertyName == nameof(m_controller.ActiveStepContent) - && m_controller.ActiveStepContent != null + else if (args.PropertyName == nameof(Controller.ActiveStepContent) + && Controller.ActiveStepContent != null && Layout == StepperLayout.Horizontal) { // there is no event raised if the Content of a ContentControl changes @@ -811,7 +825,7 @@ private void PlayHorizontalContentAnimation() /// public void Continue() { - m_controller.Continue(); + Controller.Continue(); } /// @@ -820,7 +834,7 @@ public void Continue() /// public void Back() { - m_controller.Back(); + Controller.Back(); } /// @@ -830,7 +844,7 @@ public void Back() /// public void GotoStep(int index) { - m_controller.GotoStep(index); + Controller.GotoStep(index); } /// @@ -841,7 +855,7 @@ public void GotoStep(int index) /// public void GotoStep(IStep step) { - m_controller.GotoStep(step); + Controller.GotoStep(step); } } diff --git a/MaterialDesignExtensions/Controls/TabControlAssist.cs b/MaterialDesignExtensions/Controls/TabControlAssist.cs new file mode 100644 index 00000000..9596b196 --- /dev/null +++ b/MaterialDesignExtensions/Controls/TabControlAssist.cs @@ -0,0 +1,287 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Media; + +namespace MaterialDesignExtensions.Controls +{ + /// + /// Contains attached properties for TabControl. + /// + public static class TabControlAssist + { + /// + /// The alignment of the horizontal tab headers in the TabControl. + /// + public static readonly DependencyProperty TabHeaderHorizontalAlignmentProperty = DependencyProperty.RegisterAttached( + "TabHeaderHorizontalAlignment", + typeof(HorizontalAlignment), + typeof(TabControlAssist), + new FrameworkPropertyMetadata(HorizontalAlignment.Left, FrameworkPropertyMetadataOptions.Inherits, null) + ); + + /// + /// Gets the alignment of the horizontal tab headers in the TabControl. + /// + /// + /// + public static HorizontalAlignment GetTabHeaderHorizontalAlignment(DependencyObject element) + { + return (HorizontalAlignment)element.GetValue(TabHeaderHorizontalAlignmentProperty); + } + + /// + /// Sets the alignment of the horizontal tab headers in the TabControl. + /// + /// + /// + public static void SetTabHeaderHorizontalAlignment(DependencyObject element, HorizontalAlignment value) + { + element.SetValue(TabHeaderHorizontalAlignmentProperty, value); + } + + /// + /// The alignment of the vertical tab headers in the TabControl. + /// + public static readonly DependencyProperty TabHeaderVerticalAlignmentProperty = DependencyProperty.RegisterAttached( + "TabHeaderVerticalAlignment", + typeof(VerticalAlignment), + typeof(TabControlAssist), + new FrameworkPropertyMetadata(VerticalAlignment.Top, FrameworkPropertyMetadataOptions.Inherits, null) + ); + + /// + /// Gets the alignment of the vertical tab headers in the TabControl. + /// + /// + /// + public static VerticalAlignment GetTabHeaderVerticalAlignment(DependencyObject element) + { + return (VerticalAlignment)element.GetValue(TabHeaderVerticalAlignmentProperty); + } + + /// + /// Sets the alignment of the vertical tab headers in the TabControl. + /// + /// + /// + public static void SetTabHeaderVerticalAlignment(DependencyObject element, VerticalAlignment value) + { + element.SetValue(TabHeaderVerticalAlignmentProperty, value); + } + + /// + /// The brush for not selected tab headers. + /// + public static readonly DependencyProperty TabHeaderInactiveBrushProperty = DependencyProperty.RegisterAttached( + "TabHeaderInactiveBrush", + typeof(Brush), + typeof(TabControlAssist), + new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits, null) + ); + + /// + /// Gets the brush for not selected tab headers. + /// + /// + /// + public static Brush GetTabHeaderInactiveBrush(DependencyObject element) + { + return (Brush)element.GetValue(TabHeaderInactiveBrushProperty); + } + + /// + /// Sets the brush for not selected tab headers. + /// + /// + /// + public static void SetTabHeaderInactiveBrush(DependencyObject element, Brush value) + { + element.SetValue(TabHeaderInactiveBrushProperty, value); + } + + /// + /// The opacity for not selected tab headers. + /// + public static readonly DependencyProperty TabHeaderInactiveOpacityProperty = DependencyProperty.RegisterAttached( + "TabHeaderInactiveOpacity", + typeof(double), + typeof(TabControlAssist), + new FrameworkPropertyMetadata(1.0, FrameworkPropertyMetadataOptions.Inherits, null) + ); + + /// + /// Gets the opacity for not selected tab headers. + /// + /// + /// + public static double GetTabHeaderInactiveOpacity(DependencyObject element) + { + return (double)element.GetValue(TabHeaderInactiveOpacityProperty); + } + + /// + /// Sets the ppacity for not selected tab headers. + /// + /// + /// + public static void SetTabHeaderInactiveOpacity(DependencyObject element, double value) + { + element.SetValue(TabHeaderInactiveOpacityProperty, value); + } + + /// + /// The highlight color of the selected tab item header. + /// + public static readonly DependencyProperty TabHeaderHighlightBrushProperty = DependencyProperty.RegisterAttached( + "TabHeaderHighlightBrush", + typeof(Brush), + typeof(TabControlAssist), + new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits, null) + ); + + /// + /// Gets the highlight color of the selected tab item header. + /// + /// + /// + public static Brush GetTabHeaderHighlightBrush(DependencyObject element) + { + return (Brush)element.GetValue(TabHeaderHighlightBrushProperty); + } + + /// + /// Sets the highlight color of the selected tab item header. + /// + /// + /// + public static void SetTabHeaderHighlightBrush(DependencyObject element, Brush value) + { + element.SetValue(TabHeaderHighlightBrushProperty, value); + } + + /// + /// The current color of the tab item header. Intended to be read-only. + /// + public static readonly DependencyProperty TabHeaderForegroundProperty = DependencyProperty.RegisterAttached( + "TabHeaderForeground", + typeof(Brush), + typeof(TabControlAssist), + new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits, null) + ); + + /// + /// Gets the current color of the tab item header. + /// + /// + /// + public static Brush GetTabHeaderForeground(DependencyObject element) + { + return (Brush)element.GetValue(TabHeaderForegroundProperty); + } + + /// + /// Sets the current color of the tab item header. + /// + /// + /// + public static void SetTabHeaderForeground(DependencyObject element, Brush value) + { + element.SetValue(TabHeaderForegroundProperty, value); + } + + /// + /// The current font size of the tab item header. Intended to be read-only. + /// + public static readonly DependencyProperty TabHeaderFontSizeProperty = DependencyProperty.RegisterAttached( + "TabHeaderFontSize", + typeof(double), + typeof(TabControlAssist), + new FrameworkPropertyMetadata(14.0, FrameworkPropertyMetadataOptions.Inherits, null) + ); + + /// + /// Gets the current font size of the tab item header. + /// + /// + /// + public static double GetTabHeaderFontSize(DependencyObject element) + { + return (double)element.GetValue(TabHeaderFontSizeProperty); + } + + /// + /// Sets the current font size of the tab item header. + /// + /// + /// + public static void SetTabHeaderFontSize(DependencyObject element, double value) + { + element.SetValue(TabHeaderFontSizeProperty, value); + } + + /// + /// The current font weight of the tab item header. Intended to be read-only. + /// + public static readonly DependencyProperty TabHeaderFontWeightProperty = DependencyProperty.RegisterAttached( + "TabHeaderFontWeight", + typeof(FontWeight), + typeof(TabControlAssist), + new FrameworkPropertyMetadata(FontWeights.Medium, FrameworkPropertyMetadataOptions.Inherits, null) + ); + + /// + /// Gets the current font weight of the tab item header. + /// + /// + /// + public static FontWeight GetTabHeaderFontWeight(DependencyObject element) + { + return (FontWeight)element.GetValue(TabHeaderFontWeightProperty); + } + + /// + /// Sets the current font weight of the tab item header. + /// + /// + /// + public static void SetTabHeaderFontWeight(DependencyObject element, FontWeight value) + { + element.SetValue(TabHeaderFontWeightProperty, value); + } + + + /// + /// The current margin of the tab item header's content. Intended to be read-only. + /// + public static readonly DependencyProperty TabHeaderMarginProperty = DependencyProperty.RegisterAttached( + "TabHeaderMargin", + typeof(Thickness), + typeof(TabControlAssist), + new FrameworkPropertyMetadata(new Thickness(24,12,24,12), FrameworkPropertyMetadataOptions.Inherits, null) + ); + + /// + /// Gets the current margin of the tab item header's content. + /// + /// + /// + public static Thickness GetTabHeaderMargin(DependencyObject element) + { + return (Thickness)element.GetValue(TabHeaderMarginProperty); + } + + /// + /// Sets the current margin of the tab item header's content. + /// + /// + /// + public static void SetTabHeaderMargin(DependencyObject element, Thickness value) + { + element.SetValue(TabHeaderMarginProperty, value); + } + } +} diff --git a/MaterialDesignExtensions/Controls/TabControlStepper.cs b/MaterialDesignExtensions/Controls/TabControlStepper.cs index 98224414..79f7c852 100644 --- a/MaterialDesignExtensions/Controls/TabControlStepper.cs +++ b/MaterialDesignExtensions/Controls/TabControlStepper.cs @@ -13,6 +13,7 @@ using System.Windows.Markup; using System.Windows.Media.Animation; +using MaterialDesignExtensions.Commands.Internal; using MaterialDesignExtensions.Controllers; using MaterialDesignExtensions.Model; @@ -23,26 +24,6 @@ namespace MaterialDesignExtensions.Controls /// public class TabControlStepper : TabControl, IStepper { - /// - /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. - /// - public static readonly RoutedCommand BackCommand = new RoutedCommand(); - - /// - /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. - /// - public static readonly RoutedCommand CancelCommand = new RoutedCommand(); - - /// - /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. - /// - public static readonly RoutedCommand ContinueCommand = new RoutedCommand(); - - /// - /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. - /// - public static readonly RoutedCommand StepSelectedCommand = new RoutedCommand(); - /// /// An event raised by navigating to the previous in a linear order. /// @@ -221,6 +202,28 @@ public ICommand ContinueNavigationCommand } } + /// + /// An alternative icon template done steps. + /// + public static readonly DependencyProperty DoneIconTemplateProperty = DependencyProperty.Register( + nameof(DoneIconTemplate), typeof(DataTemplate), typeof(TabControlStepper), new PropertyMetadata(null, null)); + + /// + /// An alternative icon template done steps. + /// + public DataTemplate DoneIconTemplate + { + get + { + return (DataTemplate)GetValue(DoneIconTemplateProperty); + } + + set + { + SetValue(DoneIconTemplateProperty, value); + } + } + /// /// Enables the linear mode by disabling the buttons of the header. /// The navigation must be accomplished by using the navigation commands. @@ -290,22 +293,39 @@ public ICommand StepNavigationCommand } /// - /// Gets the controller for this . - /// Must to be public for the bindings. + /// An alternative icon template to indicate validation errors. + /// + public static readonly DependencyProperty ValidationErrorIconTemplateProperty = DependencyProperty.Register( + nameof(ValidationErrorIconTemplate), typeof(DataTemplate), typeof(TabControlStepper), new PropertyMetadata(null, null)); + + /// + /// An alternative icon template to indicate validation errors. /// - public StepperController Controller + public DataTemplate ValidationErrorIconTemplate { get { - return m_controller; + return (DataTemplate)GetValue(ValidationErrorIconTemplateProperty); + } + + set + { + SetValue(ValidationErrorIconTemplateProperty, value); } } - private StepperController m_controller; + /// + /// Gets the controller for this . + /// Must to be public for the bindings. + /// + public StepperController Controller { get; private set; } static TabControlStepper() { DefaultStyleKeyProperty.OverrideMetadata(typeof(TabControlStepper), new FrameworkPropertyMetadata(typeof(TabControlStepper))); + + object selectedIndexDefaultValue = SelectedIndexProperty.GetMetadata(typeof(TabControl)).DefaultValue; + SelectedIndexProperty.OverrideMetadata(typeof(TabControlStepper), new FrameworkPropertyMetadata(selectedIndexDefaultValue, SelectedIndexChangedHandler)); } /// @@ -314,20 +334,20 @@ static TabControlStepper() public TabControlStepper() : base() { - m_controller = new StepperController(); + Controller = new StepperController(); Loaded += LoadedHandler; Unloaded += UnloadedHandler; - CommandBindings.Add(new CommandBinding(BackCommand, BackHandler)); - CommandBindings.Add(new CommandBinding(CancelCommand, CancelHandler)); - CommandBindings.Add(new CommandBinding(ContinueCommand, ContinueHandler)); - CommandBindings.Add(new CommandBinding(StepSelectedCommand, StepSelectedHandler, CanExecuteStepSelectedHandler)); + CommandBindings.Add(new CommandBinding(StepperCommands.BackCommand, BackHandler)); + CommandBindings.Add(new CommandBinding(StepperCommands.CancelCommand, CancelHandler)); + CommandBindings.Add(new CommandBinding(StepperCommands.ContinueCommand, ContinueHandler)); + CommandBindings.Add(new CommandBinding(StepperCommands.StepSelectedCommand, StepSelectedHandler, CanExecuteStepSelectedHandler)); } private void LoadedHandler(object sender, RoutedEventArgs args) { - m_controller.PropertyChanged += PropertyChangedHandler; + Controller.PropertyChanged += PropertyChangedHandler; // there is no event raised if the Content of a ContentControl changes // therefore trigger the animation in code @@ -336,7 +356,7 @@ private void LoadedHandler(object sender, RoutedEventArgs args) private void UnloadedHandler(object sender, RoutedEventArgs args) { - m_controller.PropertyChanged -= PropertyChangedHandler; + Controller.PropertyChanged -= PropertyChangedHandler; } private void InitSteps() @@ -348,7 +368,7 @@ private void InitSteps() steps.Add(new Step() { Header = tabItem.Header, Content = tabItem.Content }); } - m_controller.InitSteps(steps); + Controller.InitSteps(steps); } protected override void OnItemsChanged(NotifyCollectionChangedEventArgs args) @@ -358,9 +378,26 @@ protected override void OnItemsChanged(NotifyCollectionChangedEventArgs args) InitSteps(); } + private static void SelectedIndexChangedHandler(DependencyObject obj, DependencyPropertyChangedEventArgs args) + { + int newValue = (int)args.NewValue; + int oldValue = (int)args.OldValue; + + if (newValue != oldValue) + { + if (obj is TabControlStepper stepper) + { + if (stepper.Controller.Steps != null && newValue >= 0 && newValue < stepper.Controller.Steps.Length && newValue != stepper.Controller.GetActiveStepIndex()) + { + stepper.StepSelectedHandler(stepper.Controller.InternalSteps[newValue]); + } + } + } + } + private void BackHandler(object sender, ExecutedRoutedEventArgs args) { - StepperNavigationEventArgs navigationArgs = new StepperNavigationEventArgs(BackNavigationEvent, this, m_controller.ActiveStep, m_controller.PreviousStep, false); + StepperNavigationEventArgs navigationArgs = new StepperNavigationEventArgs(BackNavigationEvent, this, Controller.ActiveStep, Controller.PreviousStep, false); RaiseEvent(navigationArgs); if (BackNavigationCommand != null && BackNavigationCommand.CanExecute(navigationArgs)) @@ -370,7 +407,7 @@ private void BackHandler(object sender, ExecutedRoutedEventArgs args) if (!navigationArgs.Cancel) { - m_controller.Back(); + Controller.Back(); } } @@ -381,7 +418,7 @@ private void CancelHandler(object sender, ExecutedRoutedEventArgs args) return; } - StepperNavigationEventArgs navigationArgs = new StepperNavigationEventArgs(CancelNavigationEvent, this, m_controller.ActiveStep, null, false); + StepperNavigationEventArgs navigationArgs = new StepperNavigationEventArgs(CancelNavigationEvent, this, Controller.ActiveStep, null, false); RaiseEvent(navigationArgs); if (CancelNavigationCommand != null && CancelNavigationCommand.CanExecute(navigationArgs)) @@ -392,7 +429,7 @@ private void CancelHandler(object sender, ExecutedRoutedEventArgs args) private void ContinueHandler(object sender, ExecutedRoutedEventArgs args) { - StepperNavigationEventArgs navigationArgs = new StepperNavigationEventArgs(ContinueNavigationEvent, this, m_controller.ActiveStep, m_controller.NextStep, false); + StepperNavigationEventArgs navigationArgs = new StepperNavigationEventArgs(ContinueNavigationEvent, this, Controller.ActiveStep, Controller.NextStep, false); RaiseEvent(navigationArgs); if (ContinueNavigationCommand != null && ContinueNavigationCommand.CanExecute(navigationArgs)) @@ -402,7 +439,7 @@ private void ContinueHandler(object sender, ExecutedRoutedEventArgs args) if (!navigationArgs.Cancel) { - m_controller.Continue(); + Controller.Continue(); } } @@ -412,10 +449,15 @@ private void CanExecuteStepSelectedHandler(object sender, CanExecuteRoutedEventA } private void StepSelectedHandler(object sender, ExecutedRoutedEventArgs args) + { + StepSelectedHandler((StepperStepViewModel)args.Parameter); + } + + private void StepSelectedHandler(StepperStepViewModel stepperStepViewModel) { if (!IsLinear) { - StepperNavigationEventArgs navigationArgs = new StepperNavigationEventArgs(StepNavigationEvent, this, m_controller.ActiveStep, ((StepperStepViewModel)args.Parameter).Step, false); + StepperNavigationEventArgs navigationArgs = new StepperNavigationEventArgs(StepNavigationEvent, this, Controller.ActiveStep, stepperStepViewModel.Step, false); RaiseEvent(navigationArgs); if (StepNavigationCommand != null && StepNavigationCommand.CanExecute(navigationArgs)) @@ -425,20 +467,24 @@ private void StepSelectedHandler(object sender, ExecutedRoutedEventArgs args) if (!navigationArgs.Cancel) { - m_controller.GotoStep((StepperStepViewModel)args.Parameter); + Controller.GotoStep(stepperStepViewModel); } } } private void PropertyChangedHandler(object sender, PropertyChangedEventArgs args) { - if (sender == m_controller && args.PropertyName == nameof(m_controller.ActiveStepContent) - && m_controller.ActiveStepContent != null && Layout == StepperLayout.Horizontal) + if (sender == Controller && args.PropertyName == nameof(Controller.ActiveStepContent) + && Controller.ActiveStepContent != null && Layout == StepperLayout.Horizontal) { // there is no event raised if the Content of a ContentControl changes // therefore trigger the animation in code PlayHorizontalContentAnimation(); } + else if (sender == Controller && args.PropertyName == nameof(Controller.ActiveStep)) + { + SelectedIndex = Controller.GetActiveStepIndex(); + } } private void PlayHorizontalContentAnimation() diff --git a/MaterialDesignExtensions/Controls/TextBoxFileSystemPath.cs b/MaterialDesignExtensions/Controls/TextBoxFileSystemPath.cs new file mode 100644 index 00000000..601a5abc --- /dev/null +++ b/MaterialDesignExtensions/Controls/TextBoxFileSystemPath.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; + +using MaterialDesignThemes.Wpf; + +namespace MaterialDesignExtensions.Controls +{ + /// + /// Base class for a control with a text box and a button to open a file system dialog. + /// + public abstract class TextBoxFileSystemPath : Control + { + protected const string ShowDialogButtonName = "showButtonDialog"; + + /// + /// The dialog host ot show the dialog. + /// + public static readonly DependencyProperty DialogHostProperty = DependencyProperty.Register( + nameof(DialogHost), typeof(DialogHost), typeof(TextBoxFileSystemPath)); + + /// + /// The dialog host ot show the dialog. + /// + public DialogHost DialogHost + { + get + { + return (DialogHost)GetValue(DialogHostProperty); + } + + set + { + SetValue(DialogHostProperty, value); + } + } + + /// + /// The name of the dialog host to show the dialog. + /// + public static readonly DependencyProperty DialogHostNameProperty = DependencyProperty.Register( + nameof(DialogHostName), typeof(string), typeof(TextBoxFileSystemPath)); + + /// + /// The name of the dialog host to show the dialog. + /// + public string DialogHostName + { + get + { + return (string)GetValue(DialogHostNameProperty); + } + + set + { + SetValue(DialogHostNameProperty, value); + } + } + + /// + /// The for the inside the template. + /// + public static readonly DependencyProperty TextBoxStyleProperty = DependencyProperty.Register( + nameof(TextBoxStyle), typeof(Style), typeof(TextBoxFileSystemPath)); + + /// + /// The for the inside the template. + /// + public Style TextBoxStyle + { + get + { + return (Style)GetValue(TextBoxStyleProperty); + } + + set + { + SetValue(TextBoxStyleProperty, value); + } + } + + protected Button m_showButtonDialog; + + static TextBoxFileSystemPath() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(TextBoxFileSystemPath), new FrameworkPropertyMetadata(typeof(TextBoxFileSystemPath))); + } + + /// + /// Creates a new . + /// + public TextBoxFileSystemPath() + : base() + { + m_showButtonDialog = null; + } + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + if (m_showButtonDialog != null) + { + m_showButtonDialog.Click -= ShowDialogButtonClickHandler; + } + + m_showButtonDialog = Template.FindName(ShowDialogButtonName, this) as Button; + m_showButtonDialog.Click += ShowDialogButtonClickHandler; + } + + private async void ShowDialogButtonClickHandler(object sender, RoutedEventArgs args) + { + await ShowDialogAsync(); + } + + /// + /// Shows the according dialog for the control. + /// + /// + protected abstract Task ShowDialogAsync(); + } +} diff --git a/MaterialDesignExtensions/Controls/TextBoxOpenDirectory.cs b/MaterialDesignExtensions/Controls/TextBoxOpenDirectory.cs new file mode 100644 index 00000000..964be697 --- /dev/null +++ b/MaterialDesignExtensions/Controls/TextBoxOpenDirectory.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; + +namespace MaterialDesignExtensions.Controls +{ + /// + /// A control with a text box and a button to open a directory dialog. + /// + public class TextBoxOpenDirectory : TextBoxFileSystemPath + { + /// + /// The arguments for the dialog. + /// + public static readonly DependencyProperty DialogArgsProperty = DependencyProperty.Register( + nameof(DialogArgs), typeof(OpenDirectoryDialogArguments), typeof(TextBoxOpenDirectory)); + + /// + /// The arguments for the dialog. + /// + public OpenDirectoryDialogArguments DialogArgs + { + get + { + return (OpenDirectoryDialogArguments)GetValue(DialogArgsProperty); + } + + set + { + SetValue(DialogArgsProperty, value); + } + } + + /// + /// The selected directory. + /// + public static readonly DependencyProperty DirectoryProperty = DependencyProperty.Register( + nameof(Directory), typeof(string), typeof(TextBoxOpenDirectory)); + + /// + /// The selected directory. + /// + public string Directory + { + get + { + return (string)GetValue(DirectoryProperty); + } + + set + { + SetValue(DirectoryProperty, value); + } + } + + static TextBoxOpenDirectory() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(TextBoxOpenDirectory), new FrameworkPropertyMetadata(typeof(TextBoxOpenDirectory))); + } + + /// + /// Creates a new . + /// + public TextBoxOpenDirectory() : base() { } + + /// + /// Shows the according dialog for the control. + /// + /// + protected override async Task ShowDialogAsync() + { + OpenDirectoryDialogArguments args = null; + + if (DialogArgs != null) + { + args = new OpenDirectoryDialogArguments(DialogArgs); + } + else + { + args = new OpenDirectoryDialogArguments(); + } + + if (!string.IsNullOrWhiteSpace(Directory) && System.IO.Directory.Exists(Directory)) + { + args.CurrentDirectory = Directory; + } + + OpenDirectoryDialogResult result = null; + + if (DialogHost != null) + { + result = await OpenDirectoryDialog.ShowDialogAsync(DialogHost, args); + } else + { + result = await OpenDirectoryDialog.ShowDialogAsync(DialogHostName, args); + } + + if (result != null && result.Confirmed) + { + Directory = result.DirectoryInfo.FullName; + } + } + } +} diff --git a/MaterialDesignExtensions/Controls/TextBoxOpenFile.cs b/MaterialDesignExtensions/Controls/TextBoxOpenFile.cs new file mode 100644 index 00000000..63b1c5b0 --- /dev/null +++ b/MaterialDesignExtensions/Controls/TextBoxOpenFile.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; + +namespace MaterialDesignExtensions.Controls +{ + public class TextBoxOpenFile : TextBoxFileSystemPath + { + /// + /// The arguments for the dialog. + /// + public static readonly DependencyProperty DialogArgsProperty = DependencyProperty.Register( + nameof(DialogArgs), typeof(OpenFileDialogArguments), typeof(TextBoxOpenFile)); + + /// + /// The arguments for the dialog. + /// + public OpenFileDialogArguments DialogArgs + { + get + { + return (OpenFileDialogArguments)GetValue(DialogArgsProperty); + } + + set + { + SetValue(DialogArgsProperty, value); + } + } + + /// + /// The selected file. + /// + public static readonly DependencyProperty FileProperty = DependencyProperty.Register( + nameof(File), typeof(string), typeof(TextBoxOpenFile)); + + /// + /// The selected file. + /// + public string File + { + get + { + return (string)GetValue(FileProperty); + } + + set + { + SetValue(FileProperty, value); + } + } + + static TextBoxOpenFile() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(TextBoxOpenFile), new FrameworkPropertyMetadata(typeof(TextBoxOpenFile))); + } + + public TextBoxOpenFile() : base() { } + + /// + /// Shows the according dialog for the control. + /// + /// + protected override async Task ShowDialogAsync() + { + OpenFileDialogArguments args = null; + + if (DialogArgs != null) + { + args = new OpenFileDialogArguments(DialogArgs); + } + else + { + args = new OpenFileDialogArguments(); + } + + if (!string.IsNullOrWhiteSpace(File)) + { + string directory = Path.GetDirectoryName(File); + + if (!string.IsNullOrWhiteSpace(directory) && Directory.Exists(directory)) + { + args.CurrentDirectory = directory; + } + } + + OpenFileDialogResult result = null; + + if (DialogHost != null) + { + result = await OpenFileDialog.ShowDialogAsync(DialogHost, args); + } + else + { + result = await OpenFileDialog.ShowDialogAsync(DialogHostName, args); + } + + if (result != null && result.Confirmed) + { + File = result.FileInfo.FullName; + } + } + } +} diff --git a/MaterialDesignExtensions/Controls/TextBoxSaveFile.cs b/MaterialDesignExtensions/Controls/TextBoxSaveFile.cs new file mode 100644 index 00000000..6e347957 --- /dev/null +++ b/MaterialDesignExtensions/Controls/TextBoxSaveFile.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; + +namespace MaterialDesignExtensions.Controls +{ + public class TextBoxSaveFile : TextBoxFileSystemPath + { + /// + /// The arguments for the dialog. + /// + public static readonly DependencyProperty DialogArgsProperty = DependencyProperty.Register( + nameof(DialogArgs), typeof(SaveFileDialogArguments), typeof(TextBoxSaveFile)); + + /// + /// The arguments for the dialog. + /// + public SaveFileDialogArguments DialogArgs + { + get + { + return (SaveFileDialogArguments)GetValue(DialogArgsProperty); + } + + set + { + SetValue(DialogArgsProperty, value); + } + } + + /// + /// The selected file. + /// + public static readonly DependencyProperty FileProperty = DependencyProperty.Register( + nameof(File), typeof(string), typeof(TextBoxSaveFile)); + + /// + /// The selected file. + /// + public string File + { + get + { + return (string)GetValue(FileProperty); + } + + set + { + SetValue(FileProperty, value); + } + } + + static TextBoxSaveFile() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(TextBoxSaveFile), new FrameworkPropertyMetadata(typeof(TextBoxSaveFile))); + } + + public TextBoxSaveFile() : base() { } + + protected override async Task ShowDialogAsync() + { + SaveFileDialogArguments args = null; + + if (DialogArgs != null) + { + args = new SaveFileDialogArguments(DialogArgs); + } + else + { + args = new SaveFileDialogArguments(); + } + + if (!string.IsNullOrWhiteSpace(File)) + { + string directory = Path.GetDirectoryName(File); + + if (!string.IsNullOrWhiteSpace(directory) && Directory.Exists(directory)) + { + args.CurrentDirectory = directory; + } + } + + SaveFileDialogResult result = null; + + if (DialogHost != null) + { + result = await SaveFileDialog.ShowDialogAsync(DialogHost, args); + } + else + { + result = await SaveFileDialog.ShowDialogAsync(DialogHostName, args); + } + + if (result != null && result.Confirmed) + { + File = result.FileInfo.FullName; + } + } + } +} diff --git a/MaterialDesignExtensions/Controls/TextBoxSuggestions.cs b/MaterialDesignExtensions/Controls/TextBoxSuggestions.cs index aefdd493..3715e2fd 100644 --- a/MaterialDesignExtensions/Controls/TextBoxSuggestions.cs +++ b/MaterialDesignExtensions/Controls/TextBoxSuggestions.cs @@ -6,10 +6,10 @@ using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; -using System.Windows.Controls.Primitives; using System.Windows.Input; using System.Windows.Markup; +using MaterialDesignExtensions.Commands.Internal; using MaterialDesignExtensions.Controllers; using MaterialDesignExtensions.Model; @@ -25,9 +25,26 @@ public class TextBoxSuggestions : ControlWithAutocompletePopup private static readonly string SuggestionItemsPopupName = "suggestionItemsPopup"; /// - /// Internal command used by the XAML template (public to be available in the XAML template). Not intended for external usage. + /// True to keep the focus on the text box after selecting a suggestion. /// - public static readonly RoutedCommand SelectSuggestionItemCommand = new RoutedCommand(); + public static readonly DependencyProperty KeepFocusOnSelectionProperty = DependencyProperty.Register( + nameof(KeepFocusOnSelection), typeof(bool), typeof(TextBoxSuggestions), new PropertyMetadata(false)); + + /// + /// True to keep the focus on the text box after selecting a suggestion. + /// + public bool KeepFocusOnSelection + { + get + { + return (bool)GetValue(KeepFocusOnSelectionProperty); + } + + set + { + SetValue(KeepFocusOnSelectionProperty, value); + } + } /// /// The TextBox to decorate. @@ -92,7 +109,7 @@ public TextBoxSuggestions() m_autocompleteController = new AutocompleteController() { AutocompleteSource = TextBoxSuggestionsSource }; - CommandBindings.Add(new CommandBinding(SelectSuggestionItemCommand, SelectSuggestionItemCommandHandler)); + CommandBindings.Add(new CommandBinding(TextBoxSuggestionsCommands.SelectSuggestionItemCommand, SelectSuggestionItemCommandHandler)); Loaded += LoadedHandler; Unloaded += UnloadedHandler; @@ -148,7 +165,17 @@ private void SelectSuggestionItemCommandHandler(object sender, ExecutedRoutedEve { if (TextBox != null) { - TextBox.Text = args.Parameter as string; + TextBox.Text = args.Parameter as string ?? string.Empty; + + if (KeepFocusOnSelection) + { + Keyboard.Focus(TextBox); + TextBox.CaretIndex = TextBox.Text.Length; + } + else + { + Keyboard.Focus(null); + } } } @@ -174,7 +201,7 @@ private void TextBoxChangedHandler(TextBox oldTextBox, TextBox newTextBox) private void TextBoxTextChangedHandler(object sender, TextChangedEventArgs args) { - if (sender == TextBox) + if (sender == TextBox && IsEnabled && IsLoaded && TextBox.IsLoaded && TextBox.IsFocused) { m_autocompleteController?.Search(TextBox.Text); } diff --git a/MaterialDesignExtensions/Controls/TransitionContentControl.cs b/MaterialDesignExtensions/Controls/TransitionContentControl.cs new file mode 100644 index 00000000..70742231 --- /dev/null +++ b/MaterialDesignExtensions/Controls/TransitionContentControl.cs @@ -0,0 +1,203 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media.Animation; + +namespace MaterialDesignExtensions.Controls +{ + /// + /// A basic content control which uses an animation for switching its content. + /// + public class TransitionContentControl : Control + { + private const string ContentControl1Name = "ContentControl1"; + private const string ContentControl2Name = "ContentControl2"; + + private object m_lockObject = new object(); + + private bool AnimationIsRunning + { + get + { + lock (m_lockObject) + { + return m_animationIsRunning; + } + } + + set + { + lock (m_lockObject) + { + m_animationIsRunning = value; + } + } + } + + /// + /// The content of the control. + /// + public static readonly DependencyProperty ContentProperty = DependencyProperty.Register( + nameof(Content), typeof(object), typeof(TransitionContentControl), new PropertyMetadata(null, ContentPropertyChangedCallback)); + + /// + /// The content of the control. + /// + public object Content + { + get + { + return GetValue(ContentProperty); + } + + set + { + SetValue(ContentProperty, value); + } + } + + /// + /// The transition animation for switching the content. + /// + public static readonly DependencyProperty TransitionTypeProperty = DependencyProperty.Register( + nameof(TransitionType), typeof(TransitionContentControlTransitionType), typeof(TransitionContentControl), new PropertyMetadata(TransitionContentControlTransitionType.FadeInAndGrow, null)); + + /// + /// The transition animation for switching the content. + /// + public TransitionContentControlTransitionType TransitionType + { + get + { + return (TransitionContentControlTransitionType)GetValue(TransitionTypeProperty); + } + + set + { + SetValue(TransitionTypeProperty, value); + } + } + + private static void ContentPropertyChangedCallback(DependencyObject sender, DependencyPropertyChangedEventArgs args) + { + if (sender is TransitionContentControl control) + { + control.SwitchContent(args.NewValue); + } + } + + private ContentControl[] m_contentControls; + private IDictionary m_storyboards; + + private int m_foregroundIndex; + private bool m_animationIsRunning; + + static TransitionContentControl() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(TransitionContentControl), new FrameworkPropertyMetadata(typeof(TransitionContentControl))); + } + + /// + /// Creates a new . + /// + public TransitionContentControl() + : base() + { + m_contentControls = null; + m_storyboards = null; + + m_foregroundIndex = 0; + } + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + m_contentControls = new ContentControl[] + { + GetTemplateChild(ContentControl1Name) as ContentControl, + GetTemplateChild(ContentControl2Name) as ContentControl + }; + + m_storyboards = new Dictionary(); + + foreach (TransitionContentControlTransitionType transitionType in Enum.GetValues(typeof(TransitionContentControlTransitionType))) + { + m_storyboards[transitionType] = Template.Resources[GetStoryboardNameForTransitionType(transitionType)] as Storyboard; + } + } + + private async void SwitchContent(object content) + { + if (m_contentControls != null) + { + if (!AnimationIsRunning) + { + try + { + AnimationIsRunning = true; + + // set new content and push it into foreground + int currentIndex = m_foregroundIndex; + m_foregroundIndex++; + + if (m_foregroundIndex >= m_contentControls.Length) + { + m_foregroundIndex = 0; + } + + m_contentControls[m_foregroundIndex].Content = content; + + // delay the animation until the rendering of the new content is finished + // by running the code via the Dispatcher with a priority lower than the rendering + await Dispatcher.Invoke(async () => + { + // move the new content into foreground + Panel.SetZIndex(m_contentControls[m_foregroundIndex], Panel.GetZIndex(m_contentControls[currentIndex]) + 1); + Panel.SetZIndex(m_contentControls[currentIndex], 0); + + // start the animation + Storyboard storyboard = m_storyboards[TransitionType]; + storyboard.Begin(m_contentControls[m_foregroundIndex]); + + // wait until the animation completes and finally remove the hidden content to free memory + // add a short delay to the animation duration + int animationDuration = (int)storyboard.Children.Max(x => x.Duration.TimeSpan.TotalMilliseconds); + + await Task.Delay(animationDuration + 50); + + m_contentControls[currentIndex].Content = null; + }, System.Windows.Threading.DispatcherPriority.Loaded); + } + finally + { + AnimationIsRunning = false; + } + } + else + { + m_contentControls[m_foregroundIndex].Content = content; + } + } + } + + private string GetStoryboardNameForTransitionType(TransitionContentControlTransitionType transitionType) + { + return $"{Enum.GetName(typeof(TransitionContentControlTransitionType), transitionType)}Storyboard"; + } + } + + /// + /// The available animations for . + /// + public enum TransitionContentControlTransitionType : byte + { + FadeIn, + Grow, + FadeInAndGrow + } +} diff --git a/MaterialDesignExtensions/Converters/AsyncImageTask.cs b/MaterialDesignExtensions/Converters/AsyncImageTask.cs index e94a00c6..dd477dd9 100644 --- a/MaterialDesignExtensions/Converters/AsyncImageTask.cs +++ b/MaterialDesignExtensions/Converters/AsyncImageTask.cs @@ -18,41 +18,70 @@ namespace MaterialDesignExtensions.Converters /// public class AsyncImageTask : INotifyPropertyChanged { + private readonly object m_lockObject = new object(); + /// /// The property changed event. /// public event PropertyChangedEventHandler PropertyChanged; - + + private object m_image; + /// /// The loaded image. /// - public object Image { get; private set; } + public object Image + { + get + { + lock (m_lockObject) + { + return m_image; + } + } + + private set + { + lock (m_lockObject) + { + if (m_image != value) + { + m_image = value; + + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Image))); + } + } + } + } /// /// Creates a new AsyncImageTask to load the image with the specified width and height keeping its ratio. /// - /// - /// - /// + /// The full filename of the image + /// The target width of the image + /// The target height of the image + /// True to enable caching public AsyncImageTask(string imageFilename, int targetWidth = 40, int targetHeight = 40, bool useCache = false) { - Image = PackIconKind.FileImage; + m_image = PackIconKind.FileImage; LoadImageAsync(imageFilename, targetWidth, targetHeight, useCache); } private async void LoadImageAsync(string imageFilename, int targetWidth, int targetHeight, bool useCache) { - object image = await Task.Run(() => BitmapImageHelper.LoadImage(imageFilename, targetWidth, targetHeight, useCache)); - - if (image == null) + await Task.Run(async () => { - image = PackIconKind.Image; - } - - Image = image; +#if DEBUG + Console.WriteLine($"get image {imageFilename}"); +#endif + BitmapImage image = await BitmapImageHelper.LoadImageAsync(imageFilename, targetWidth, targetHeight, useCache).ConfigureAwait(false); - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Image))); + if (image != null) + { + Image = image; + } + }).ConfigureAwait(false); } } } diff --git a/MaterialDesignExtensions/Converters/BoolToVisibilityConverter.cs b/MaterialDesignExtensions/Converters/BoolToVisibilityConverter.cs index 74e4fbda..25f71e5e 100644 --- a/MaterialDesignExtensions/Converters/BoolToVisibilityConverter.cs +++ b/MaterialDesignExtensions/Converters/BoolToVisibilityConverter.cs @@ -9,12 +9,24 @@ namespace MaterialDesignExtensions.Converters { + /// + /// Converter to map a boolean to a . + /// public class BoolToVisibilityConverter : IValueConverter { + /// + /// The visibility value if the argument is false. + /// public Visibility FalseValue { get; set; } + /// + /// The visibility value if the argument is true. + /// public Visibility TrueValue { get; set; } + /// + /// Creates a new . + /// public BoolToVisibilityConverter() { FalseValue = Visibility.Collapsed; diff --git a/MaterialDesignExtensions/Converters/BooleanAndConverter.cs b/MaterialDesignExtensions/Converters/BooleanAndConverter.cs index a1039e41..227aff09 100644 --- a/MaterialDesignExtensions/Converters/BooleanAndConverter.cs +++ b/MaterialDesignExtensions/Converters/BooleanAndConverter.cs @@ -8,8 +8,14 @@ namespace MaterialDesignExtensions.Converters { + /// + /// Converter to apply boolean "and" operation. + /// public class BooleanAndConverter : IMultiValueConverter { + /// + /// Creates a new . + /// public BooleanAndConverter() { } public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) diff --git a/MaterialDesignExtensions/Converters/BooleanAndToVisibilityConverter.cs b/MaterialDesignExtensions/Converters/BooleanAndToVisibilityConverter.cs index 19f67cb9..3c8536e9 100644 --- a/MaterialDesignExtensions/Converters/BooleanAndToVisibilityConverter.cs +++ b/MaterialDesignExtensions/Converters/BooleanAndToVisibilityConverter.cs @@ -9,11 +9,17 @@ namespace MaterialDesignExtensions.Converters { + /// + /// Converter to apply boolean "and" operation and map to a . + /// public class BooleanAndToVisibilityConverter : IMultiValueConverter { private BooleanAndConverter m_booleanAndConverter; private BoolToVisibilityConverter m_boolToVisibilityConverter; + /// + /// The visibility value if the argument is false. + /// public Visibility FalseValue { get @@ -27,6 +33,9 @@ public Visibility FalseValue } } + /// + /// The visibility value if the argument is true. + /// public Visibility TrueValue { get @@ -40,6 +49,9 @@ public Visibility TrueValue } } + /// + /// Creates a new . + /// public BooleanAndToVisibilityConverter() { m_booleanAndConverter = new BooleanAndConverter(); diff --git a/MaterialDesignExtensions/Converters/BooleanOrConverter.cs b/MaterialDesignExtensions/Converters/BooleanOrConverter.cs index aa25ba93..56145559 100644 --- a/MaterialDesignExtensions/Converters/BooleanOrConverter.cs +++ b/MaterialDesignExtensions/Converters/BooleanOrConverter.cs @@ -8,8 +8,14 @@ namespace MaterialDesignExtensions.Converters { + /// + /// Converter to apply boolean "or" operation. + /// public class BooleanOrConverter : IMultiValueConverter { + /// + /// Creates a new . + /// public BooleanOrConverter() { } public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) diff --git a/MaterialDesignExtensions/Converters/BooleanOrToVisibilityConverter.cs b/MaterialDesignExtensions/Converters/BooleanOrToVisibilityConverter.cs index 4414ea1a..2b395946 100644 --- a/MaterialDesignExtensions/Converters/BooleanOrToVisibilityConverter.cs +++ b/MaterialDesignExtensions/Converters/BooleanOrToVisibilityConverter.cs @@ -9,11 +9,17 @@ namespace MaterialDesignExtensions.Converters { + /// + /// Converter to apply boolean "and" operation and map to a . + /// public class BooleanOrToVisibilityConverter : IMultiValueConverter { private BooleanOrConverter m_booleanOrConverter; private BoolToVisibilityConverter m_boolToVisibilityConverter; + /// + /// The visibility value if the argument is false. + /// public Visibility FalseValue { get @@ -27,6 +33,9 @@ public Visibility FalseValue } } + /// + /// The visibility value if the argument is true. + /// public Visibility TrueValue { get @@ -40,6 +49,9 @@ public Visibility TrueValue } } + /// + /// Creates a new . + /// public BooleanOrToVisibilityConverter() { m_booleanOrConverter = new BooleanOrConverter(); diff --git a/MaterialDesignExtensions/Converters/DateTimeAgoConverter.cs b/MaterialDesignExtensions/Converters/DateTimeAgoConverter.cs index de2d5a1a..9db62273 100644 --- a/MaterialDesignExtensions/Converters/DateTimeAgoConverter.cs +++ b/MaterialDesignExtensions/Converters/DateTimeAgoConverter.cs @@ -8,8 +8,14 @@ namespace MaterialDesignExtensions.Converters { + /// + /// Converter for displaying a past . + /// public class DateTimeAgoConverter : IValueConverter { + /// + /// Creates a new . + /// public DateTimeAgoConverter() { } public object Convert(object value, Type targetType, object parameter, CultureInfo culture) @@ -19,7 +25,7 @@ public object Convert(object value, Type targetType, object parameter, CultureIn DateTime now = DateTime.Now; TimeSpan timeSpan = now - dateTime; - if (timeSpan.TotalHours < 24) + if (now.Date == dateTime.Date && timeSpan.TotalHours < 24) { return dateTime.ToShortTimeString(); } diff --git a/MaterialDesignExtensions/Converters/EmptyEnumerableToBoolConverter.cs b/MaterialDesignExtensions/Converters/EmptyEnumerableToBoolConverter.cs index bfabf9c4..7849dbb5 100644 --- a/MaterialDesignExtensions/Converters/EmptyEnumerableToBoolConverter.cs +++ b/MaterialDesignExtensions/Converters/EmptyEnumerableToBoolConverter.cs @@ -9,10 +9,19 @@ namespace MaterialDesignExtensions.Converters { + /// + /// Maps a null or empty to a boolean value. + /// public class EmptyEnumerableToBoolConverter : IValueConverter { + /// + /// The boolean value, if the is empty or null. + /// public bool EmptyValue { get; set; } + /// + /// Creates a new . + /// public EmptyEnumerableToBoolConverter() { } public object Convert(object value, Type targetType, object parameter, CultureInfo culture) diff --git a/MaterialDesignExtensions/Converters/EmptyEnumerableVisibilityConverter.cs b/MaterialDesignExtensions/Converters/EmptyEnumerableVisibilityConverter.cs index 74fb806c..955a7109 100644 --- a/MaterialDesignExtensions/Converters/EmptyEnumerableVisibilityConverter.cs +++ b/MaterialDesignExtensions/Converters/EmptyEnumerableVisibilityConverter.cs @@ -10,12 +10,24 @@ namespace MaterialDesignExtensions.Converters { + /// + /// Maps a null or empty to a . + /// public class EmptyEnumerableVisibilityConverter : IValueConverter { + /// + /// The visibility value of an empty or null . + /// public Visibility EmptyVisibility { get; set; } + /// + /// The visibility value of a non empty . + /// public Visibility NotEmptyVisibility { get; set; } + /// + /// Creates a new . + /// public EmptyEnumerableVisibilityConverter() { EmptyVisibility = Visibility.Collapsed; diff --git a/MaterialDesignExtensions/Converters/FileFiltersTypeConverter.cs b/MaterialDesignExtensions/Converters/FileFiltersTypeConverter.cs index 4f42b544..96230c07 100644 --- a/MaterialDesignExtensions/Converters/FileFiltersTypeConverter.cs +++ b/MaterialDesignExtensions/Converters/FileFiltersTypeConverter.cs @@ -11,8 +11,14 @@ namespace MaterialDesignExtensions.Converters { + /// + /// Converter for mappings between and . + /// public class FileFiltersTypeConverter : TypeConverter { + /// + /// Creates a new . + /// public FileFiltersTypeConverter() { } public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) diff --git a/MaterialDesignExtensions/Converters/FileSizeConverter.cs b/MaterialDesignExtensions/Converters/FileSizeConverter.cs index 7f67380c..6869021b 100644 --- a/MaterialDesignExtensions/Converters/FileSizeConverter.cs +++ b/MaterialDesignExtensions/Converters/FileSizeConverter.cs @@ -8,8 +8,14 @@ namespace MaterialDesignExtensions.Converters { + /// + /// Converts a file size of bytes into a user friendly string. + /// public class FileSizeConverter : IValueConverter { + /// + /// Creates a new . + /// public FileSizeConverter() { } public object Convert(object value, Type targetType, object parameter, CultureInfo culture) diff --git a/MaterialDesignExtensions/Converters/FileSystemInfoIconConverter.cs b/MaterialDesignExtensions/Converters/FileSystemInfoIconConverter.cs index 5368df95..a85eb163 100644 --- a/MaterialDesignExtensions/Converters/FileSystemInfoIconConverter.cs +++ b/MaterialDesignExtensions/Converters/FileSystemInfoIconConverter.cs @@ -11,21 +11,45 @@ using MaterialDesignExtensions.Controllers; +// use Pri.LongPath classes instead of System.IO for the MaterialDesignExtensions.LongPath build to support long file system paths on older Windows and .NET versions +#if LONG_PATH +using DirectoryInfo = Pri.LongPath.DirectoryInfo; +using FileInfo = Pri.LongPath.FileInfo; +#endif + namespace MaterialDesignExtensions.Converters { + /// + /// Converts a file extension to a content related icon. + /// public class FileSystemInfoIconConverter : IValueConverter { private ISet m_imageFileExtensions; private IDictionary m_contentForFileExtension; + /// + /// The content mode to control the type of icons or thumbnails for image file types. + /// public FileSystemInfoIconConverterImageMode ImageMode { get; set; } + /// + /// The width of a loaded thumbnail image. + /// public int ImageTargetWidth { get; set; } + /// + /// The height of a loaded thumbnail image. + /// public int ImageTargetHeight { get; set; } + /// + /// True to use a cache for loaded images. + /// public bool UseCache { get; set; } + /// + /// Creates a new . + /// public FileSystemInfoIconConverter() { ImageMode = FileSystemInfoIconConverterImageMode.Icon; @@ -48,15 +72,15 @@ public FileSystemInfoIconConverter() // code ["cs"] = PackIconKind.LanguageCsharp, ["xaml"] = PackIconKind.Xml, - ["ts"] = PackIconKind.CodeBraces, + ["ts"] = PackIconKind.LanguageTypescript, ["js"] = PackIconKind.LanguageJavascript, - ["java"] = PackIconKind.CodeBraces, + ["java"] = PackIconKind.LanguageJava, ["c"] = PackIconKind.LanguageC, ["cpp"] = PackIconKind.LanguageCpp, ["h"] = PackIconKind.CodeBraces, ["py"] = PackIconKind.LanguagePython, ["php"] = PackIconKind.LanguagePhp, - ["r"] = PackIconKind.CodeBraces, + ["r"] = PackIconKind.LanguageR, // compiled code and executables ["exe"] = PackIconKind.Settings, @@ -74,7 +98,7 @@ public FileSystemInfoIconConverter() ["htm"] = PackIconKind.Xml, ["css"] = PackIconKind.CodeTags, ["xml"] = PackIconKind.Xml, - ["json"] = PackIconKind.Json, + ["json"] = PackIconKind.CodeJson, ["xsl"] = PackIconKind.Xml, ["xslt"] = PackIconKind.Xml, ["sql"] = PackIconKind.Database, @@ -198,10 +222,24 @@ private object GetContentForFile(string filename) } } + /// + /// The content mode to control the type of icons or thumbnails for image file types. + /// public enum FileSystemInfoIconConverterImageMode : byte { + /// + /// Only use icons + /// Icon, + + /// + /// Use thumbnails for images + /// Image, + + /// + /// Use async task to load thumbnail images + /// AsyncImageTask } } diff --git a/MaterialDesignExtensions/Converters/FileSystemInfoPackIconColorConverter.cs b/MaterialDesignExtensions/Converters/FileSystemInfoPackIconColorConverter.cs index ecfc24f1..def2f44b 100644 --- a/MaterialDesignExtensions/Converters/FileSystemInfoPackIconColorConverter.cs +++ b/MaterialDesignExtensions/Converters/FileSystemInfoPackIconColorConverter.cs @@ -11,10 +11,16 @@ namespace MaterialDesignExtensions.Converters { + /// + /// Converts a to a special color for displaying it. + /// public class FileSystemInfoPackIconColorConverter : IValueConverter { private IDictionary m_brushesForPackIcon; + /// + /// Creates a new . + /// public FileSystemInfoPackIconColorConverter() { m_brushesForPackIcon = new Dictionary() diff --git a/MaterialDesignExtensions/Converters/NotNullBooleanConverter.cs b/MaterialDesignExtensions/Converters/NotNullBooleanConverter.cs index eeeaa18e..31bc9c4c 100644 --- a/MaterialDesignExtensions/Converters/NotNullBooleanConverter.cs +++ b/MaterialDesignExtensions/Converters/NotNullBooleanConverter.cs @@ -8,8 +8,15 @@ namespace MaterialDesignExtensions.Converters { + /// + /// Converts a not null value to true, otherwise false. + /// A special logic for strings will be applied to handle empty or whitespace only strings like null values. + /// public class NotNullBooleanConverter : IValueConverter { + /// + /// Creates a new . + /// public NotNullBooleanConverter() { } public object Convert(object value, Type targetType, object parameter, CultureInfo culture) diff --git a/MaterialDesignExtensions/Converters/NotNullToVisibilityConverter.cs b/MaterialDesignExtensions/Converters/NotNullToVisibilityConverter.cs index c14c8d42..afdb6f96 100644 --- a/MaterialDesignExtensions/Converters/NotNullToVisibilityConverter.cs +++ b/MaterialDesignExtensions/Converters/NotNullToVisibilityConverter.cs @@ -9,10 +9,19 @@ namespace MaterialDesignExtensions.Converters { + /// + /// Converts null or not null to a . + /// public class NotNullToVisibilityConverter : IValueConverter { + /// + /// The visibility value if the argument is null. + /// public Visibility NullValue { get; set; } + /// + /// Creates a new . + /// public NotNullToVisibilityConverter() { NullValue = Visibility.Collapsed; diff --git a/MaterialDesignExtensions/Converters/NullToVisibilityConverter.cs b/MaterialDesignExtensions/Converters/NullToVisibilityConverter.cs index 7db51c48..46afb2e4 100644 --- a/MaterialDesignExtensions/Converters/NullToVisibilityConverter.cs +++ b/MaterialDesignExtensions/Converters/NullToVisibilityConverter.cs @@ -9,12 +9,24 @@ namespace MaterialDesignExtensions.Converters { + /// + /// Converts a null value to a . + /// public class NullToVisibilityConverter : IValueConverter { + /// + /// The visibility value for a null value. + /// public Visibility NullValue { get; set; } + /// + /// The visibility value for a not null value. + /// public Visibility NotNullValue { get; set; } + /// + /// Creates a new . + /// public NullToVisibilityConverter() { NullValue = Visibility.Collapsed; diff --git a/MaterialDesignExtensions/Converters/ObjectCollectionToVisibilityConverter.cs b/MaterialDesignExtensions/Converters/ObjectCollectionToVisibilityConverter.cs index 9daa0c5c..1de9ace9 100644 --- a/MaterialDesignExtensions/Converters/ObjectCollectionToVisibilityConverter.cs +++ b/MaterialDesignExtensions/Converters/ObjectCollectionToVisibilityConverter.cs @@ -11,10 +11,19 @@ namespace MaterialDesignExtensions.Converters { + /// + /// Converts an empty collection to a . + /// public class ObjectCollectionToVisibilityConverter : IValueConverter { + /// + /// The visibility value of an empty collection. + /// public Visibility EmptyValue { get; set; } + /// + /// Creates a new . + /// public ObjectCollectionToVisibilityConverter() { EmptyValue = Visibility.Collapsed; diff --git a/MaterialDesignExtensions/Converters/ObjectHasTypeConverter.cs b/MaterialDesignExtensions/Converters/ObjectHasTypeConverter.cs index 64c06c0d..58c4ba15 100644 --- a/MaterialDesignExtensions/Converters/ObjectHasTypeConverter.cs +++ b/MaterialDesignExtensions/Converters/ObjectHasTypeConverter.cs @@ -8,24 +8,21 @@ namespace MaterialDesignExtensions.Converters { + /// + /// Checks the type of the argument if it is of a specified type. + /// public class ObjectHasTypeConverter : IMultiValueConverter { + /// + /// The fully qualified name of the type. + /// public string FullTypeName { get; set; } + /// + /// Creates a new . + /// public ObjectHasTypeConverter() { } - /*public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - string valueType = value != null ? value.GetType().FullName : "null"; - - return valueType == FullTypeName; - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - return Binding.DoNothing; - }*/ - public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { if (values != null && values.Length == 2) diff --git a/MaterialDesignExtensions/Converters/ObjectToTypeStringConverter.cs b/MaterialDesignExtensions/Converters/ObjectToTypeStringConverter.cs index 6e498928..bd70467f 100644 --- a/MaterialDesignExtensions/Converters/ObjectToTypeStringConverter.cs +++ b/MaterialDesignExtensions/Converters/ObjectToTypeStringConverter.cs @@ -8,8 +8,14 @@ namespace MaterialDesignExtensions.Converters { + /// + /// Converts an object to its fully qualified type name. + /// public class ObjectToTypeStringConverter : IValueConverter { + /// + /// Creates a new . + /// public ObjectToTypeStringConverter() { } public object Convert(object value, Type targetType, object parameter, CultureInfo culture) diff --git a/MaterialDesignExtensions/Converters/StepIconTemplateConverter.cs b/MaterialDesignExtensions/Converters/StepIconTemplateConverter.cs new file mode 100644 index 00000000..91645e6a --- /dev/null +++ b/MaterialDesignExtensions/Converters/StepIconTemplateConverter.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Data; + +using MaterialDesignExtensions.Controls; +using MaterialDesignExtensions.Model; +using MaterialDesignExtensions.TemplateSelectors; + +namespace MaterialDesignExtensions.Converters +{ + /// + /// A converter triggering a for selecting an item for a step. + /// This converter is necessary to react on changing data of the stepper and steps to call the selector logic again. + /// + public class StepIconTemplateConverter : IMultiValueConverter + { + private StepIconTemplateSelector _iconTemplateSelector; + + /// + /// Creates a new . + /// + public StepIconTemplateConverter() + { + _iconTemplateSelector = new StepIconTemplateSelector(); + } + + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + if (values == null) + { + throw new ArgumentNullException(nameof(values)); + } + + IStepper stepper = values[0] as IStepper; + FrameworkElement element = values[1] as FrameworkElement; + StepperStepViewModel stepViewModel = values[2] as StepperStepViewModel; + + return _iconTemplateSelector.SelectTemplate(stepper, element, stepViewModel); + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/MaterialDesignExtensions/Converters/UpperCaseConverter.cs b/MaterialDesignExtensions/Converters/UpperCaseConverter.cs index bb421d80..0546c4f1 100644 --- a/MaterialDesignExtensions/Converters/UpperCaseConverter.cs +++ b/MaterialDesignExtensions/Converters/UpperCaseConverter.cs @@ -8,8 +8,14 @@ namespace MaterialDesignExtensions.Converters { + /// + /// Converts a string to upper case. + /// public class UpperCaseConverter : IValueConverter { + /// + /// Creates a new . + /// public UpperCaseConverter() { } public object Convert(object value, Type targetType, object parameter, CultureInfo culture) diff --git a/MaterialDesignExtensions/Converters/WindowCaptionButtonBaseConverter.cs b/MaterialDesignExtensions/Converters/WindowCaptionButtonBaseConverter.cs new file mode 100644 index 00000000..6b448302 --- /dev/null +++ b/MaterialDesignExtensions/Converters/WindowCaptionButtonBaseConverter.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Data; + +namespace MaterialDesignExtensions.Converters +{ + /// + /// Base class for the converters controlling the visibility and enabled state of window caption buttons. + /// + public abstract class WindowCaptionButtonBaseConverter : IMultiValueConverter + { + /// + /// Identifier for the minimize caption button. + /// + public string MinimizeButtonName => "minimizeButton"; + + /// + /// Identifier for the maximize/restore caption button. + /// + public string MaximizeRestoreButtonName => "maximizeRestoreButton"; + + /// + /// Identifier for the close caption button. + /// + public string CloseButtonName => "closeButton"; + + /// + /// Creates a new . + /// + public WindowCaptionButtonBaseConverter() { } + + public abstract object Convert(object[] values, Type targetType, object parameter, CultureInfo culture); + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/MaterialDesignExtensions/Converters/WindowCaptionButtonEnabledConverter.cs b/MaterialDesignExtensions/Converters/WindowCaptionButtonEnabledConverter.cs new file mode 100644 index 00000000..a5916364 --- /dev/null +++ b/MaterialDesignExtensions/Converters/WindowCaptionButtonEnabledConverter.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; + +namespace MaterialDesignExtensions.Converters +{ + /// + /// Converts a of a window into an enabled state of an according caption button. + /// + public class WindowCaptionButtonEnabledConverter : WindowCaptionButtonBaseConverter + { + /// + /// Creates a new . + /// + public WindowCaptionButtonEnabledConverter() : base() { } + + public override object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + try + { + if (values != null && values.Length == 2) + { + string buttonName = (string)values[0]; + ResizeMode resizeMode = (ResizeMode)values[1]; + + if (buttonName == CloseButtonName) + { + return true; + } + else if (buttonName == MinimizeButtonName) + { + return resizeMode != ResizeMode.NoResize; + } + else if (buttonName == MaximizeRestoreButtonName) + { + return resizeMode != ResizeMode.NoResize && resizeMode != ResizeMode.CanMinimize; + } + } + } + catch (Exception) + { + // use the default return value below + } + + return true; + } + } +} diff --git a/MaterialDesignExtensions/Converters/WindowCaptionButtonVisibilityConverter.cs b/MaterialDesignExtensions/Converters/WindowCaptionButtonVisibilityConverter.cs new file mode 100644 index 00000000..f5978f42 --- /dev/null +++ b/MaterialDesignExtensions/Converters/WindowCaptionButtonVisibilityConverter.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; + +namespace MaterialDesignExtensions.Converters +{ + /// + /// Converts a and a of a window into a of an according caption button. + /// + public class WindowCaptionButtonVisibilityConverter : WindowCaptionButtonBaseConverter + { + /// + /// Creates a new . + /// + public WindowCaptionButtonVisibilityConverter() : base() { } + + public override object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + try + { + if (values != null && values.Length == 3) + { + string buttonName = (string)values[0]; + WindowStyle windowStyle = (WindowStyle)values[1]; + ResizeMode resizeMode = (ResizeMode)values[2]; + + if (buttonName == CloseButtonName) + { + if (windowStyle != WindowStyle.None) + { + return Visibility.Visible; + } + else + { + return Visibility.Collapsed; + } + } + else + { + if (resizeMode != ResizeMode.NoResize + && (windowStyle == WindowStyle.SingleBorderWindow || windowStyle == WindowStyle.ThreeDBorderWindow)) + { + return Visibility.Visible; + } + else + { + return Visibility.Collapsed; + } + } + } + } + catch (Exception) + { + // use the default return value below + } + + return Visibility.Visible; + } + } +} diff --git a/MaterialDesignExtensions/Converters/WindowTitleBarIconVisibilityConverter.cs b/MaterialDesignExtensions/Converters/WindowTitleBarIconVisibilityConverter.cs new file mode 100644 index 00000000..93492cb6 --- /dev/null +++ b/MaterialDesignExtensions/Converters/WindowTitleBarIconVisibilityConverter.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Data; + +namespace MaterialDesignExtensions.Converters +{ + /// + /// Converts a and icon of a window into a for the icon. + /// + public class WindowTitleBarIconVisibilityConverter : IMultiValueConverter + { + /// + /// Creates a new . + /// + public WindowTitleBarIconVisibilityConverter() { } + + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + try + { + if (values != null && values.Length == 2) + { + object icon = values[0]; + WindowStyle windowStyle = (WindowStyle)values[1]; + + if (icon != null && (windowStyle == WindowStyle.SingleBorderWindow || windowStyle == WindowStyle.ThreeDBorderWindow)) + { + return Visibility.Visible; + } + else + { + return Visibility.Collapsed; + } + } + } + catch (Exception) + { + // use the default return value below + } + + return Visibility.Visible; + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/MaterialDesignExtensions/Converters/WindowTitleVisibilityConverter.cs b/MaterialDesignExtensions/Converters/WindowTitleVisibilityConverter.cs new file mode 100644 index 00000000..799e6373 --- /dev/null +++ b/MaterialDesignExtensions/Converters/WindowTitleVisibilityConverter.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Data; + +namespace MaterialDesignExtensions.Converters +{ + /// + /// Converts a of a window into a of the whole title bar. + /// + public class WindowTitleVisibilityConverter : IMultiValueConverter + { + /// + /// Creates a new . + /// + public WindowTitleVisibilityConverter() { } + + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + try + { + if (values != null && values.Length >= 1) + { + WindowStyle windowStyle = (WindowStyle)values[0]; + + if (windowStyle != WindowStyle.None) + { + return Visibility.Visible; + } + else + { + return Visibility.Collapsed; + } + } + } + catch (Exception) + { + // use the default return value below + } + + return Visibility.Visible; + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/MaterialDesignExtensions/Localization/Strings.Designer.cs b/MaterialDesignExtensions/Localization/Strings.Designer.cs index 232eb964..baf4ad4a 100644 --- a/MaterialDesignExtensions/Localization/Strings.Designer.cs +++ b/MaterialDesignExtensions/Localization/Strings.Designer.cs @@ -19,7 +19,7 @@ namespace MaterialDesignExtensions.Localization { // -Klasse über ein Tool wie ResGen oder Visual Studio automatisch generiert. // Um einen Member hinzuzufügen oder zu entfernen, bearbeiten Sie die .ResX-Datei und führen dann ResGen // mit der /str-Option erneut aus, oder Sie erstellen Ihr VS-Projekt neu. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] public class Strings { @@ -87,6 +87,15 @@ public static string Close { } } + /// + /// Sucht eine lokalisierte Zeichenfolge, die Create ähnelt. + /// + public static string Create { + get { + return ResourceManager.GetString("Create", resourceCulture); + } + } + /// /// Sucht eine lokalisierte Zeichenfolge, die Created ähnelt. /// @@ -105,6 +114,15 @@ public static string Desktop { } } + /// + /// Sucht eine lokalisierte Zeichenfolge, die Details ähnelt. + /// + public static string Details { + get { + return ResourceManager.GetString("Details", resourceCulture); + } + } + /// /// Sucht eine lokalisierte Zeichenfolge, die Folder '{0}' not found ähnelt. /// @@ -204,6 +222,24 @@ public static string LongPathsAreNotSupported { } } + /// + /// Sucht eine lokalisierte Zeichenfolge, die Maximize ähnelt. + /// + public static string Maximize { + get { + return ResourceManager.GetString("Maximize", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Minimize ähnelt. + /// + public static string Minimize { + get { + return ResourceManager.GetString("Minimize", resourceCulture); + } + } + /// /// Sucht eine lokalisierte Zeichenfolge, die Music ähnelt. /// @@ -213,6 +249,42 @@ public static string Music { } } + /// + /// Sucht eine lokalisierte Zeichenfolge, die New directory name ähnelt. + /// + public static string NewDirectoryName { + get { + return ResourceManager.GetString("NewDirectoryName", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die No selected directories ähnelt. + /// + public static string NoSelectedDirectories { + get { + return ResourceManager.GetString("NoSelectedDirectories", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die No selected files ähnelt. + /// + public static string NoSelectedFiles { + get { + return ResourceManager.GetString("NoSelectedFiles", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die OK ähnelt. + /// + public static string Ok { + get { + return ResourceManager.GetString("Ok", resourceCulture); + } + } + /// /// Sucht eine lokalisierte Zeichenfolge, die Open ähnelt. /// @@ -240,6 +312,24 @@ public static string Pictures { } } + /// + /// Sucht eine lokalisierte Zeichenfolge, die Remove ähnelt. + /// + public static string Remove { + get { + return ResourceManager.GetString("Remove", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die Restore ähnelt. + /// + public static string Restore { + get { + return ResourceManager.GetString("Restore", resourceCulture); + } + } + /// /// Sucht eine lokalisierte Zeichenfolge, die Save ähnelt. /// @@ -285,6 +375,42 @@ public static string SelectDirectory { } } + /// + /// Sucht eine lokalisierte Zeichenfolge, die Selection ähnelt. + /// + public static string Selection { + get { + return ResourceManager.GetString("Selection", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die The directory name is invalid ähnelt. + /// + public static string TheDirectoryNameIsInvalid { + get { + return ResourceManager.GetString("TheDirectoryNameIsInvalid", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die The directory name must not be empty ähnelt. + /// + public static string TheDirectoryNameMustNotBeEmpty { + get { + return ResourceManager.GetString("TheDirectoryNameMustNotBeEmpty", resourceCulture); + } + } + + /// + /// Sucht eine lokalisierte Zeichenfolge, die The directory '{0}' already exists ähnelt. + /// + public static string TheDirectoryXAlreadyExists { + get { + return ResourceManager.GetString("TheDirectoryXAlreadyExists", resourceCulture); + } + } + /// /// Sucht eine lokalisierte Zeichenfolge, die User ähnelt. /// diff --git a/MaterialDesignExtensions/Localization/Strings.ar-DZ.Designer.cs b/MaterialDesignExtensions/Localization/Strings.ar-DZ.Designer.cs new file mode 100644 index 00000000..5f282702 --- /dev/null +++ b/MaterialDesignExtensions/Localization/Strings.ar-DZ.Designer.cs @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/MaterialDesignExtensions/Localization/Strings.ar-DZ.resx b/MaterialDesignExtensions/Localization/Strings.ar-DZ.resx new file mode 100644 index 00000000..99ced4c8 --- /dev/null +++ b/MaterialDesignExtensions/Localization/Strings.ar-DZ.resx @@ -0,0 +1,243 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + تم رفض الوصول إلى المجلد "{0}" + + + إلغاء + + + إغلاق + + + منشأ + + + سطح المكتب + + + لم يتم العثور على المجلد "{0}" + + + المستندات + + + الأقراص + + + مجلد فارغ + + + الملفات + + + حجم الملف + + + المجلدات + + + آخر ولوج + + + آخر تعديل + + + قرص محلي + + + الموسيقى + + + فتح + + + فتح ملف + + + الصور + + + حفظ + + + حفظ الملف + + + تحديد + + + إختيار مجلد + + + المستخدم + + + الفيديوهات + + + البحث + + + المسارات الطويلة غير مدعومة + + + اسم الدليل غير صالح + + + يجب ألا يكون اسم الدليل فارغًا + + + الدليل '{0}' موجود بالفعل + + + إنشاء + + + إسم دليل جديد + + + تكبير + + + تصغير + + + إستعادة + + + التفاصيل + + + حذف + + + إختيار + + + لم يتم تحديد مجلدات + + + لم يتم تحديد ملفات + + + موافق + + \ No newline at end of file diff --git a/MaterialDesignExtensions/Localization/Strings.cs-cz.Designer.cs b/MaterialDesignExtensions/Localization/Strings.cs-cz.Designer.cs new file mode 100644 index 00000000..5f282702 --- /dev/null +++ b/MaterialDesignExtensions/Localization/Strings.cs-cz.Designer.cs @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/MaterialDesignExtensions/Localization/Strings.cs-cz.resx b/MaterialDesignExtensions/Localization/Strings.cs-cz.resx new file mode 100644 index 00000000..98da819f --- /dev/null +++ b/MaterialDesignExtensions/Localization/Strings.cs-cz.resx @@ -0,0 +1,243 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Přístup ke složce '{0}' odepřen + + + Zrušit + + + Zavřít + + + Vytvořeno + + + Plocha + + + Složka '{0}' nebyla nalezena + + + Dokumenty + + + Disky + + + Prázdná složka + + + Soubory + + + Velikost souboru + + + Složky + + + Naposledy otevřeno + + + Naposledy upraveno + + + Místní disk + + + Hudba + + + Otevřít + + + Otevřít soubor + + + Obrázky + + + Uložit + + + Uložit soubor + + + Vybrat + + + Vybrat složku + + + Uživatel + + + Videa + + + Hledat + + + Dlouhé cesty nejsou podporovány + + + Název složky je neplatný + + + Název složky nesmí být prázdný + + + Složka '{0}' již existuje + + + Vytvořit + + + Nový název složky + + + Maximalizovat + + + Minimalizovat + + + Obnovit + + + Podrobnosti + + + Odstranit + + + Výběr + + + Žádné vybrané složky + + + Žádné vybrané soubory + + + OK + + diff --git a/MaterialDesignExtensions/Localization/Strings.de.resx b/MaterialDesignExtensions/Localization/Strings.de.resx index bf3bc26f..3839712b 100644 --- a/MaterialDesignExtensions/Localization/Strings.de.resx +++ b/MaterialDesignExtensions/Localization/Strings.de.resx @@ -198,4 +198,43 @@ Lange Pfade werden nicht unterstützt + + Der Ordnername ist ungültig + + + Der Ordername darf nicht leer sein + + + Der Ordner "{0}" existiert bereits + + + Erstellen + + + Neuer Ordnername + + + Maximieren + + + Minimieren + + + Verkleinern + + + Details + + + Entfernen + + + Auswahl + + + Keine ausgewählten Ordner + + + Keine ausgewählten Dateien + \ No newline at end of file diff --git a/MaterialDesignExtensions/Localization/Strings.fr-FR.Designer.cs b/MaterialDesignExtensions/Localization/Strings.fr-FR.Designer.cs new file mode 100644 index 00000000..5f282702 --- /dev/null +++ b/MaterialDesignExtensions/Localization/Strings.fr-FR.Designer.cs @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/MaterialDesignExtensions/Localization/Strings.fr-FR.resx b/MaterialDesignExtensions/Localization/Strings.fr-FR.resx new file mode 100644 index 00000000..e338dcc5 --- /dev/null +++ b/MaterialDesignExtensions/Localization/Strings.fr-FR.resx @@ -0,0 +1,243 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Accès au dossier '{0}' refusé + + + Annuler + + + Fermer + + + Créé + + + Bureau + + + Dossier '{0}' introuvable + + + Documents + + + Disques + + + Dossier vide + + + Dossiers + + + Taille du fichier + + + Dossiers + + + Dernier accès + + + Dernière modification + + + Disque local + + + Musique + + + Ouvrir + + + Ouvrir fichier + + + Photos + + + Enregistrer + + + Enregistrer le fichier + + + Sélectionner + + + Sélectionner le dossier + + + Utilisateur + + + Vidéos + + + Chercher + + + Longs chemins ne sont pas pris en charge + + + Nom du répertoire n'est pas valide + + + Nom du répertoire ne doit pas être vide + + + Répertoire '{0}' existe déjà + + + Créer + + + Nouveau nom de répertoire + + + Maximiser + + + Minimiser + + + Restaurer + + + Détails + + + Supprimer + + + Sélection + + + Aucun répertoire sélectionné + + + Aucun fichier sélectionné + + + OK + + \ No newline at end of file diff --git a/MaterialDesignExtensions/Localization/Strings.ja-JP.Designer.cs b/MaterialDesignExtensions/Localization/Strings.ja-JP.Designer.cs new file mode 100644 index 00000000..5f282702 --- /dev/null +++ b/MaterialDesignExtensions/Localization/Strings.ja-JP.Designer.cs @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/MaterialDesignExtensions/Localization/Strings.ja-JP.resx b/MaterialDesignExtensions/Localization/Strings.ja-JP.resx new file mode 100644 index 00000000..3da11e1c --- /dev/null +++ b/MaterialDesignExtensions/Localization/Strings.ja-JP.resx @@ -0,0 +1,243 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + フォルダー '{0}' へのアクセスが拒否されました。 + + + キャンセル + + + 閉じる + + + 作成日時 + + + デスクトップ + + + フォルダー '{0}' が見つかりませんでした。 + + + ドキュメント + + + ドライブ + + + 空のフォルダー + + + ファイル + + + ファイルサイズ + + + フォルダー + + + アクセス日時 + + + 更新日時 + + + ローカル ディスク + + + ミュージック + + + 開く + + + ファイルを開く + + + ピクチャ + + + 保存 + + + ファイルの保存 + + + 選択 + + + フォルダーの選択 + + + ユーザー + + + ビデオ + + + 検索 + + + ロングパスネームはサポートされていません。 + + + ディレクトリ名が無効です。 + + + ディレクトリ名は空にできません。 + + + ディレクトリ '{0}' は既に存在します。 + + + 作成 + + + 新しいフォルダー + + + 最大化 + + + 最小化 + + + 元に戻す + + + 詳細 + + + 削除 + + + 選択 + + + フォルダーが選択されていません。 + + + ファイルが選択されていません。 + + + OK + + \ No newline at end of file diff --git a/MaterialDesignExtensions/Localization/Strings.pt-BR.Designer.cs b/MaterialDesignExtensions/Localization/Strings.pt-BR.Designer.cs new file mode 100644 index 00000000..5f282702 --- /dev/null +++ b/MaterialDesignExtensions/Localization/Strings.pt-BR.Designer.cs @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/MaterialDesignExtensions/Localization/Strings.pt-BR.resx b/MaterialDesignExtensions/Localization/Strings.pt-BR.resx new file mode 100644 index 00000000..636c9e74 --- /dev/null +++ b/MaterialDesignExtensions/Localization/Strings.pt-BR.resx @@ -0,0 +1,243 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Acesso negado à pasta '{0}' + + + Cancelar + + + Fechar + + + Criar + + + Criado + + + Área de Trabalho + + + Detalhe + + + Pasta '{0}' não encontrada + + + Documentos + + + Unidades + + + Pasta Vazia + + + Arquivos + + + Tamanho do Arquivo + + + Pastas + + + Último acesso + + + Última modificação + + + Unidade Local + + + Caminhos longos não são suportados + + + Maximizar + + + Minimizar + + + Música + + + Novo nome de diretório + + + Nenhum diretório selecionado + + + Nenhum arquivo selecionado + + + OK + + + Abrir + + + Abrir Arquivo + + + Imagens + + + Remover + + + Restaurar + + + Salvar + + + Salvar Arquivo + + + Pesquisar + + + Selecionar + + + Selecionar Pasta + + + Seleção + + + O nome do diretório não é válido + + + O nome do diretório não pode ficar vazio + + + O diretório '{0}' já existe + + + Usuário + + + Vídeos + + \ No newline at end of file diff --git a/MaterialDesignExtensions/Localization/Strings.resx b/MaterialDesignExtensions/Localization/Strings.resx index 81c903a2..af205b88 100644 --- a/MaterialDesignExtensions/Localization/Strings.resx +++ b/MaterialDesignExtensions/Localization/Strings.resx @@ -198,4 +198,46 @@ Long paths are not supported + + The directory name is invalid + + + The directory name must not be empty + + + The directory '{0}' already exists + + + Create + + + New directory name + + + Maximize + + + Minimize + + + Restore + + + Details + + + Remove + + + Selection + + + No selected directories + + + No selected files + + + OK + \ No newline at end of file diff --git a/MaterialDesignExtensions/Localization/Strings.ru-ru.Designer.cs b/MaterialDesignExtensions/Localization/Strings.ru-ru.Designer.cs new file mode 100644 index 00000000..e69de29b diff --git a/MaterialDesignExtensions/Localization/Strings.ru-ru.resx b/MaterialDesignExtensions/Localization/Strings.ru-ru.resx new file mode 100644 index 00000000..26c8f2cf --- /dev/null +++ b/MaterialDesignExtensions/Localization/Strings.ru-ru.resx @@ -0,0 +1,240 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Доступ к папке '{0}' запрещен + + + Отмена + + + Закрыть + + + Создать + + + Создан + + + Рабочий стол + + + Информация + + + Папка '{0}' не найдена + + + Документы + + + Устройства и диски + + + Пустая папка + + + Файлы + + + Размер файла + + + Папки + + + Открыт + + + Изменен + + + Локальный диск + + + Длинные пути не поддерживаются + + + Развернуть + + + Свернуть + + + Музыка + + + Название новой директории + + + Не выбраны директории + + + Не выбраны файлы + + + Открыть + + + Открыть файл + + + Изображения + + + Удалить + + + Восстановить + + + Сохранить + + + Сохранить файл + + + Поиск + + + Выбрать + + + Выбрать папку + + + Выбрано + + + Некорректное наименование директории + + + Наименование директории не должно быть пустым + + + Директория '{0}' уже существует + + + Этот компьютер + + + Видео + + \ No newline at end of file diff --git a/MaterialDesignExtensions/Localization/Strings.uz-Latn-UZ.Designer.cs b/MaterialDesignExtensions/Localization/Strings.uz-Latn-UZ.Designer.cs new file mode 100644 index 00000000..e69de29b diff --git a/MaterialDesignExtensions/Localization/Strings.uz-Latn-UZ.resx b/MaterialDesignExtensions/Localization/Strings.uz-Latn-UZ.resx new file mode 100644 index 00000000..7d433e7d --- /dev/null +++ b/MaterialDesignExtensions/Localization/Strings.uz-Latn-UZ.resx @@ -0,0 +1,201 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + '{0}' papkaga kirish rad etildi + + + Bekor qilish + + + Yopish + + + Yaratildi + + + Ish stoli + + + '{0}' papka topilmadi + + + Hujjatlar + + + Drayvlar + + + Bo'sh papka + + + Fayllar + + + Fayl hajmi + + + Papkalar + + + So'nggi kirish + + + Oxirgi o'zgartirishlar kiritilgan + + + Mahalliy draiver + + + Musiqa + + + Ochish + + + Faylni ochish + + + Rasmlar + + + Saqlash + + + Faylni saqlash + + + Tanlash + + + Papkani tanlash + + + Foydalanuvchi + + + Videolar + + + Qidirish + + + Fayl joylashgan joyga yo'l juda uzun bo'lsa, qo'llab-quvvatlanmaydi + + \ No newline at end of file diff --git a/MaterialDesignExtensions/Localization/Strings.zh-CN.Designer.cs b/MaterialDesignExtensions/Localization/Strings.zh-CN.Designer.cs new file mode 100644 index 00000000..e69de29b diff --git a/MaterialDesignExtensions/Localization/Strings.zh-CN.resx b/MaterialDesignExtensions/Localization/Strings.zh-CN.resx new file mode 100644 index 00000000..3e3b5635 --- /dev/null +++ b/MaterialDesignExtensions/Localization/Strings.zh-CN.resx @@ -0,0 +1,243 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 访问文件夹 '{0}' 被拒绝 + + + 取消 + + + 关闭 + + + 创建 + + + 桌面 + + + 文件夹 '{0}' 未找到 + + + 文档 + + + 磁盘 + + + 空文件夹 + + + 文件 + + + 文件大小 + + + 文件夹 + + + 最近访问 + + + 最近修改 + + + 本地磁盘 + + + 音乐 + + + 打开 + + + 打开文件 + + + 图片 + + + 保存 + + + 保存文件 + + + 选择 + + + 选择文件夹 + + + 用户 + + + 视频 + + + 搜索 + + + 不支持长路径 + + + 文件夹名称不合法 + + + 文件夹名称不能为空 + + + 文件夹 '{0}' 已存在 + + + 创建 + + + 新文件夹名称 + + + 最大化 + + + 最小化 + + + 恢复 + + + 详细 + + + 删除 + + + 选择 + + + 没有选中文件夹 + + + 没有选中文件 + + + 确定 + + \ No newline at end of file diff --git a/MaterialDesignExtensions/MaterialDesignExtensions.LongPath.nuspec b/MaterialDesignExtensions/MaterialDesignExtensions.LongPath.nuspec new file mode 100644 index 00000000..8c5c0194 --- /dev/null +++ b/MaterialDesignExtensions/MaterialDesignExtensions.LongPath.nuspec @@ -0,0 +1,44 @@ + + + + MaterialDesignExtensions.LongPath + 3.0.0-a02 + MaterialDesignExtensions.LongPath + Philipp Spiegel + Philipp Spiegel + https://spiegelp.github.io/MaterialDesignExtensions/#license + + + https://spiegelp.github.io/MaterialDesignExtensions/ + https://raw.githubusercontent.com/spiegelp/MaterialDesignExtensions/master/icon/icon.png + + false + Special build of MaterialDesignExtensions supporting long file system paths via an additional library rather than System.IO. + Special build of MaterialDesignExtensions supporting long file system paths via an additional library rather than System.IO to support older Windows and .NET versions. + +If you do not need the long path support for older Windows and .NET version, please install the original Material Design Extensions package (https://www.nuget.org/packages/MaterialDesignExtensions/). + https://spiegelp.github.io/MaterialDesignExtensions/#releasenotes + +Important notice: The configuration of Material Design Extensions changed in order to enable changing the theme at runtime. Please change your configuration according to App.xaml of the demo. + + Copyright (c) 2017-2019 Spiegel Philipp + Material Design UI WPF XAML MaterialDesign MD Stepper SideNavigation AppBar Autocomplete FileDialog DirectoryDialog Spinner MaterialWindow Window LongPath + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MaterialDesignExtensions/MaterialDesignExtensions.csproj b/MaterialDesignExtensions/MaterialDesignExtensions.csproj index 888a8b6a..ab064fae 100644 --- a/MaterialDesignExtensions/MaterialDesignExtensions.csproj +++ b/MaterialDesignExtensions/MaterialDesignExtensions.csproj @@ -1,236 +1,136 @@  - - + + - Debug - AnyCPU - {809632DA-5EB8-4EE8-AD71-57239701550C} - library - MaterialDesignExtensions - MaterialDesignExtensions - v4.5 - 512 - {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 4 - + net462;net7.0-windows + true + Material Design Extensions + Material Design Extensions is based on Material Design in XAML Toolkit to provide additional controls and features for WPF apps. The controls might not be specified in the Material Design specification or would crash the scope of Material Design in XAML Toolkit. + Material Design Extensions is based on Material Design in XAML Toolkit to provide additional controls and features for WPF apps. The controls might not be specified in the Material Design specification or would crash the scope of Material Design in XAML Toolkit. + +Important notice: The configuration for version 2.6.0 changed. See the release notes of this version for details. + Philipp Spiegel + Philipp Spiegel + Material Design Extensions + Copyright © 2017-2021 + 4.0.0.0 + 4.0.0.0 + 4.0.0 + https://spiegelp.github.io/MaterialDesignExtensions/ + https://github.com/spiegelp/MaterialDesignExtensions + git + Material Design UI WPF XAML MaterialDesign MD Stepper SideNavigation AppBar Autocomplete FileDialog DirectoryDialog Spinner MaterialWindow Window NavigationRail BusyOverlay + true + key.snk - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - + + + bin\$(Configuration)\$(TargetFramework)\MaterialDesignExtensions.xml - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - bin\Release\MaterialDesignExtensions.xml + + + bin\$(Configuration)\$(TargetFramework)\MaterialDesignExtensions.xml + - - ..\packages\MaterialDesignColors.1.1.3\lib\net45\MaterialDesignColors.dll - - - ..\packages\MaterialDesignThemes.2.4.0.1044\lib\net45\MaterialDesignThemes.Wpf.dll - - - - - - - - - - - 4.0 - - - - + + + - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - MSBuild:Compile - Designer - - - Designer - MSBuild:Compile - - - MSBuild:Compile - Designer - - - MSBuild:Compile - Designer - - - Designer - MSBuild:Compile - - - MSBuild:Compile - Designer - - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - - - MSBuild:Compile - Designer - - - Designer - MSBuild:Compile - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + True True + Strings.resx + + True + True Strings.de.resx - + + True True + Strings.ru-RU.resx + + True - Strings.resx + True + Strings.uz-Latn-UZ.resx - - - - - - - - - - - - - - - - - - - - - - - - - Code + + True + True + Strings.cs-cz.resx + + + True + True + Strings.pt-BR.resx + + + True + True + Strings.fr-FR.resx - + + True True + Strings.ar-DZ.resx + + True - Resources.resx + True + Strings.ja-JP.resx - + + True True - Settings.settings - True + Strings.zh-CN.resx - - - - - - - + + + + PublicResXFileCodeGenerator + Strings.Designer.cs + + + ResXFileCodeGenerator Strings.de.Designer.cs - - PublicResXFileCodeGenerator - Strings.Designer.cs + + ResXFileCodeGenerator + Strings.ru-RU.Designer.cs + + + ResXFileCodeGenerator + Strings.uz-Latn-UZ.Designer.cs + + + ResXFileCodeGenerator + Strings.cs-cz.Designer.cs + + + ResXFileCodeGenerator + Strings.pt-BR.Designer.cs + + + ResXFileCodeGenerator + Strings.fr-FR.Designer.cs + + + ResXFileCodeGenerator + Strings.ar-DZ.Designer.cs + + + ResXFileCodeGenerator + Strings.ja-JP.Designer.cs - + ResXFileCodeGenerator - Resources.Designer.cs + Strings.zh-CN.Designer.cs - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - + \ No newline at end of file diff --git a/MaterialDesignExtensions/MaterialDesignExtensions.nuspec b/MaterialDesignExtensions/MaterialDesignExtensions.nuspec index 7fb442f2..11327230 100644 --- a/MaterialDesignExtensions/MaterialDesignExtensions.nuspec +++ b/MaterialDesignExtensions/MaterialDesignExtensions.nuspec @@ -2,26 +2,64 @@ MaterialDesignExtensions - 2.4.0 + 4.0.0-a02 Material Design Extensions Philipp Spiegel Philipp Spiegel https://spiegelp.github.io/MaterialDesignExtensions/#license + + https://spiegelp.github.io/MaterialDesignExtensions/ https://raw.githubusercontent.com/spiegelp/MaterialDesignExtensions/master/icon/icon.png + false + Material Design Extensions is based on Material Design in XAML Toolkit to provide additional controls and features for WPF apps. The controls might not be specified in the Material Design specification or would crash the scope of Material Design in XAML Toolkit. Material Design Extensions is based on Material Design in XAML Toolkit to provide additional controls and features for WPF apps. The controls might not be specified in the Material Design specification or would crash the scope of Material Design in XAML Toolkit. https://spiegelp.github.io/MaterialDesignExtensions/#releasenotes - Copyright (c) 2017-2018 Spiegel Philipp - Material Design UI WPF XAML MaterialDesign MD Stepper SideNavigation AppBar Autocomplete FileDialog + + Copyright (c) 2017-2021 Spiegel Philipp + Material Design UI WPF XAML MaterialDesign MD Stepper SideNavigation AppBar Autocomplete FileDialog DirectoryDialog Spinner MaterialWindow MaterialNavigationWindow Window NavigationRail BusyOverlay - + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MaterialDesignExtensions/Model/BaseNavigationItem.cs b/MaterialDesignExtensions/Model/BaseNavigationItem.cs index 0528cf4a..436407cf 100644 --- a/MaterialDesignExtensions/Model/BaseNavigationItem.cs +++ b/MaterialDesignExtensions/Model/BaseNavigationItem.cs @@ -7,6 +7,9 @@ namespace MaterialDesignExtensions.Model { + /// + /// Base class for an item in a . + /// public abstract class BaseNavigationItem : INavigationItem { public event PropertyChangedEventHandler PropertyChanged; @@ -15,6 +18,9 @@ public abstract class BaseNavigationItem : INavigationItem protected bool m_isSelected; protected NavigationItemSelectedCallback m_callback; + /// + /// True, if the user can select this navigation item. + /// public virtual bool IsSelectable { get @@ -28,6 +34,9 @@ public virtual bool IsSelectable } } + /// + /// True, if the navigation item is selected. + /// public virtual bool IsSelected { get @@ -43,6 +52,10 @@ public virtual bool IsSelected } } + /// + /// An optional callback method raised, when this navigation item will be selected. + /// This API is necessary because events are not async. + /// public virtual NavigationItemSelectedCallback NavigationItemSelectedCallback { get @@ -58,6 +71,9 @@ public virtual NavigationItemSelectedCallback NavigationItemSelectedCallback } } + /// + /// Creates a new . + /// public BaseNavigationItem() { m_isSelectable = true; diff --git a/MaterialDesignExtensions/Model/DirectoryInfoItem.cs b/MaterialDesignExtensions/Model/DirectoryInfoItem.cs index 155519f4..cff64a6a 100644 --- a/MaterialDesignExtensions/Model/DirectoryInfoItem.cs +++ b/MaterialDesignExtensions/Model/DirectoryInfoItem.cs @@ -5,10 +5,21 @@ using System.Text; using System.Threading.Tasks; +// use Pri.LongPath classes instead of System.IO for the MaterialDesignExtensions.LongPath build to support long file system paths on older Windows and .NET versions +#if LONG_PATH +using DirectoryInfo = Pri.LongPath.DirectoryInfo; +#endif + namespace MaterialDesignExtensions.Model { + /// + /// A directory item in the current directory list of file system controls. + /// public class DirectoryInfoItem : FileSystemEntryItem { + /// + /// Creates a new . + /// public DirectoryInfoItem() : base() { } } } diff --git a/MaterialDesignExtensions/Model/DividerNavigationItem.cs b/MaterialDesignExtensions/Model/DividerNavigationItem.cs index 9edab558..9ff287b6 100644 --- a/MaterialDesignExtensions/Model/DividerNavigationItem.cs +++ b/MaterialDesignExtensions/Model/DividerNavigationItem.cs @@ -6,8 +6,14 @@ namespace MaterialDesignExtensions.Model { + /// + /// A divider line in a . + /// public class DividerNavigationItem : NotSelectableNavigationItem { + /// + /// Creates a new . + /// public DividerNavigationItem() : base() { } } } diff --git a/MaterialDesignExtensions/Model/FileFilter.cs b/MaterialDesignExtensions/Model/FileFilter.cs index bd06ae6e..1ac9766c 100644 --- a/MaterialDesignExtensions/Model/FileFilter.cs +++ b/MaterialDesignExtensions/Model/FileFilter.cs @@ -9,6 +9,11 @@ using MaterialDesignExtensions.Controllers; +// use Pri.LongPath classes instead of System.IO for the MaterialDesignExtensions.LongPath build to support long file system paths on older Windows and .NET versions +#if LONG_PATH +using FileInfo = Pri.LongPath.FileInfo; +#endif + namespace MaterialDesignExtensions.Model { /// diff --git a/MaterialDesignExtensions/Model/FileInfoItem.cs b/MaterialDesignExtensions/Model/FileInfoItem.cs index 940a4b27..3a7bd8b7 100644 --- a/MaterialDesignExtensions/Model/FileInfoItem.cs +++ b/MaterialDesignExtensions/Model/FileInfoItem.cs @@ -5,10 +5,21 @@ using System.Text; using System.Threading.Tasks; +// use Pri.LongPath classes instead of System.IO for the MaterialDesignExtensions.LongPath build to support long file system paths on older Windows and .NET versions +#if LONG_PATH +using FileInfo = Pri.LongPath.FileInfo; +#endif + namespace MaterialDesignExtensions.Model { + /// + /// A file item in the current directory list of file system controls. + /// public class FileInfoItem : FileSystemEntryItem { + /// + /// Creates a new . + /// public FileInfoItem() : base() { } } } diff --git a/MaterialDesignExtensions/Model/FileSystemEntriesGroupHeader.cs b/MaterialDesignExtensions/Model/FileSystemEntriesGroupHeader.cs index 13d6e985..69188157 100644 --- a/MaterialDesignExtensions/Model/FileSystemEntriesGroupHeader.cs +++ b/MaterialDesignExtensions/Model/FileSystemEntriesGroupHeader.cs @@ -7,6 +7,9 @@ namespace MaterialDesignExtensions.Model { + /// + /// A group header in the items lists of file system controls to group directories and files. + /// public class FileSystemEntriesGroupHeader : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; @@ -14,6 +17,9 @@ public class FileSystemEntriesGroupHeader : INotifyPropertyChanged private string m_header; private bool m_showSeparator; + /// + /// The header label. + /// public string Header { get @@ -32,6 +38,9 @@ public string Header } } + /// + /// True, to display a separator above the label. + /// public bool ShowSeparator { get @@ -50,6 +59,9 @@ public bool ShowSeparator } } + /// + /// Creates a new . + /// public FileSystemEntriesGroupHeader() { m_header = null; diff --git a/MaterialDesignExtensions/Model/FileSystemEntryItem.cs b/MaterialDesignExtensions/Model/FileSystemEntryItem.cs index 3c3736bf..526b77d7 100644 --- a/MaterialDesignExtensions/Model/FileSystemEntryItem.cs +++ b/MaterialDesignExtensions/Model/FileSystemEntryItem.cs @@ -6,8 +6,16 @@ using System.Text; using System.Threading.Tasks; +// use Pri.LongPath classes instead of System.IO for the MaterialDesignExtensions.LongPath build to support long file system paths on older Windows and .NET versions +#if LONG_PATH +using FileSystemInfo = Pri.LongPath.FileSystemInfo; +#endif + namespace MaterialDesignExtensions.Model { + /// + /// An item in the current directory list of file system controls. + /// public abstract class FileSystemEntryItem : INotifyPropertyChanged where T : FileSystemInfo { public event PropertyChangedEventHandler PropertyChanged; @@ -15,6 +23,9 @@ public abstract class FileSystemEntryItem : INotifyPropertyChanged where T : private bool m_isSelected; private T m_value; + /// + /// True, if the list item is selected. + /// public bool IsSelected { get @@ -33,6 +44,9 @@ public bool IsSelected } } + /// + /// The value (directory or file) of the lsit item. + /// public T Value { get @@ -51,6 +65,9 @@ public T Value } } + /// + /// Creates a new . + /// public FileSystemEntryItem() { m_isSelected = false; diff --git a/MaterialDesignExtensions/Model/FirstLevelNavigationItem.cs b/MaterialDesignExtensions/Model/FirstLevelNavigationItem.cs index 761b92a7..1e13ba42 100644 --- a/MaterialDesignExtensions/Model/FirstLevelNavigationItem.cs +++ b/MaterialDesignExtensions/Model/FirstLevelNavigationItem.cs @@ -6,8 +6,14 @@ namespace MaterialDesignExtensions.Model { + /// + /// A first level navigation item of a . + /// public class FirstLevelNavigationItem : NavigationItem { + /// + /// Creates a new . + /// public FirstLevelNavigationItem() : base() { } } } diff --git a/MaterialDesignExtensions/Model/IAutocompleteSource.cs b/MaterialDesignExtensions/Model/IAutocompleteSource.cs index 7330469d..fbb227d8 100644 --- a/MaterialDesignExtensions/Model/IAutocompleteSource.cs +++ b/MaterialDesignExtensions/Model/IAutocompleteSource.cs @@ -7,22 +7,56 @@ namespace MaterialDesignExtensions.Model { + /// + /// Interface for a autocomplete source as needed by controls for data binding. + /// public interface IAutocompleteSource { + /// + /// Does the search for the autocomplete. + /// + /// The term to search for + /// The items found for the search term IEnumerable Search(string searchTerm); } + /// + /// Generic version of the interface to work with type safety. + /// + /// public interface IAutocompleteSource : IAutocompleteSource { + /// + /// Does the search for the autocomplete. + /// + /// The term to search for + /// The items found for the search term new IEnumerable Search(string searchTerm); } + /// + /// Base class for autocomplete sources providing default implementations of the necessary interfaces for the controls. + /// + /// public abstract class AutocompleteSource : IAutocompleteSource { + /// + /// Creates a new . + /// public AutocompleteSource() { } + /// + /// Does the search for the autocomplete. + /// + /// The term to search for + /// The items found for the search term public abstract IEnumerable Search(string searchTerm); + /// + /// Does the search for the autocomplete. + /// + /// The term to search for + /// The items found for the search term IEnumerable IAutocompleteSource.Search(string searchTerm) { return Search(searchTerm); diff --git a/MaterialDesignExtensions/Model/IAutocompleteSourceChangingItems.cs b/MaterialDesignExtensions/Model/IAutocompleteSourceChangingItems.cs index e2fa4446..410025f1 100644 --- a/MaterialDesignExtensions/Model/IAutocompleteSourceChangingItems.cs +++ b/MaterialDesignExtensions/Model/IAutocompleteSourceChangingItems.cs @@ -7,17 +7,37 @@ namespace MaterialDesignExtensions.Model { + /// + /// An interface to notify the autocomplete control, that the underlying data source changed. + /// public interface IAutocompleteSourceChangingItems : IAutocompleteSource { + /// + /// An event to notify the autocomplete control, that the underlying data source changed. + /// event AutocompleteSourceItemsChangedEventHandler AutocompleteSourceItemsChanged; } + /// + /// Generic version of interface to work with type safety. + /// + /// public interface IAutocompleteSourceChangingItems : IAutocompleteSourceChangingItems, IAutocompleteSource { } + /// + /// Base class to notify the autocomplete control, that the underlying data source changed. + /// + /// public abstract class AutocompleteSourceChangingItems : AutocompleteSource, IAutocompleteSourceChangingItems { + /// + /// An event to notify the autocomplete control, that the underlying data source changed. + /// public event AutocompleteSourceItemsChangedEventHandler AutocompleteSourceItemsChanged; + /// + /// Creates a new . + /// public AutocompleteSourceChangingItems() : base() { } protected void OnAutocompleteSourceItemsChanged() @@ -26,10 +46,21 @@ protected void OnAutocompleteSourceItemsChanged() } } + /// + /// The delegate for handling the event. + /// + /// + /// public delegate void AutocompleteSourceItemsChangedEventHandler(object sender, AutocompleteSourceItemsChangedEventArgs args); + /// + /// The argument for the event. + /// public class AutocompleteSourceItemsChangedEventArgs : EventArgs { + /// + /// Creates a new . + /// public AutocompleteSourceItemsChangedEventArgs() : base() { } } } diff --git a/MaterialDesignExtensions/Model/INavigationItem.cs b/MaterialDesignExtensions/Model/INavigationItem.cs index 927d50e4..d51f2b69 100644 --- a/MaterialDesignExtensions/Model/INavigationItem.cs +++ b/MaterialDesignExtensions/Model/INavigationItem.cs @@ -7,14 +7,32 @@ namespace MaterialDesignExtensions.Model { + /// + /// The basic interface for an item of a . + /// public interface INavigationItem : INotifyPropertyChanged { + /// + /// True, if the user can select this navigation item. + /// bool IsSelectable { get; set; } + /// + /// True, if the navigation item is selected. + /// bool IsSelected { get; set; } + /// + /// An optional callback method raised, when this navigation item will be selected. + /// This API is necessary because events are not async. + /// NavigationItemSelectedCallback NavigationItemSelectedCallback { get; set; } } + /// + /// The delegate for a method. + /// + /// + /// public delegate object NavigationItemSelectedCallback(INavigationItem navigationItem); } diff --git a/MaterialDesignExtensions/Model/ITextBoxSuggestionsSource.cs b/MaterialDesignExtensions/Model/ITextBoxSuggestionsSource.cs index fc4fd384..ee528fa1 100644 --- a/MaterialDesignExtensions/Model/ITextBoxSuggestionsSource.cs +++ b/MaterialDesignExtensions/Model/ITextBoxSuggestionsSource.cs @@ -7,16 +7,35 @@ namespace MaterialDesignExtensions.Model { + /// + /// Special version of for the controls. + /// public interface ITextBoxSuggestionsSource : IAutocompleteSource { } + /// + /// Base class for text box suggestion sources providing default implementations of the necessary interface. + /// public abstract class TextBoxSuggestionsSource : ITextBoxSuggestionsSource { + /// + /// Creates a new . + /// public TextBoxSuggestionsSource() { } + /// + /// Does the search controls. + /// + /// The term to search for + /// The items found for the search term public abstract IEnumerable Search(string searchTerm); + /// + /// Does the search for the controls. + /// + /// The term to search for + /// The items found for the search term IEnumerable IAutocompleteSource.Search(string searchTerm) { return Search(searchTerm); diff --git a/MaterialDesignExtensions/Model/NavigationItem.cs b/MaterialDesignExtensions/Model/NavigationItem.cs index d1190b9b..bbedc475 100644 --- a/MaterialDesignExtensions/Model/NavigationItem.cs +++ b/MaterialDesignExtensions/Model/NavigationItem.cs @@ -3,14 +3,22 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Windows; namespace MaterialDesignExtensions.Model { + /// + /// An navigation item of a . + /// public class NavigationItem : BaseNavigationItem { private object m_icon; + private DataTemplate m_iconTemplate; private string m_label; + /// + /// The icon of this navigation item. + /// public object Icon { get @@ -26,6 +34,27 @@ public object Icon } } + /// + /// An optional template for the icon. + /// + public DataTemplate IconTemplate + { + get + { + return m_iconTemplate; + } + + set + { + m_iconTemplate = value; + + OnPropertyChanged(nameof(IconTemplate)); + } + } + + /// + /// The label of this navigation item. + /// public string Label { get @@ -41,6 +70,9 @@ public string Label } } + /// + /// Creates a new . + /// public NavigationItem() { m_icon = null; diff --git a/MaterialDesignExtensions/Model/NotSelectableNavigationItem.cs b/MaterialDesignExtensions/Model/NotSelectableNavigationItem.cs index 9da4498d..43853ad4 100644 --- a/MaterialDesignExtensions/Model/NotSelectableNavigationItem.cs +++ b/MaterialDesignExtensions/Model/NotSelectableNavigationItem.cs @@ -6,8 +6,14 @@ namespace MaterialDesignExtensions.Model { + /// + /// The base class for not selectable items (headers, dividers) in a . + /// public abstract class NotSelectableNavigationItem : BaseNavigationItem { + /// + /// True, if the user can select this navigation item. + /// public override bool IsSelectable { get @@ -18,6 +24,9 @@ public override bool IsSelectable set { } } + /// + /// True, if the navigation item is selected. + /// public override bool IsSelected { get @@ -28,6 +37,10 @@ public override bool IsSelected set { } } + /// + /// The delegate for a method. + /// + /// public override NavigationItemSelectedCallback NavigationItemSelectedCallback { get @@ -38,6 +51,9 @@ public override NavigationItemSelectedCallback NavigationItemSelectedCallback set { } } + /// + /// Creates a new . + /// public NotSelectableNavigationItem() : base() { } } } diff --git a/MaterialDesignExtensions/Model/SearchSuggestionItem.cs b/MaterialDesignExtensions/Model/SearchSuggestionItem.cs index fa93a2e5..c930af38 100644 --- a/MaterialDesignExtensions/Model/SearchSuggestionItem.cs +++ b/MaterialDesignExtensions/Model/SearchSuggestionItem.cs @@ -7,6 +7,9 @@ namespace MaterialDesignExtensions.Model { + /// + /// An item out of the suggestions of a search control. + /// public class SearchSuggestionItem : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; @@ -14,6 +17,9 @@ public class SearchSuggestionItem : INotifyPropertyChanged private bool m_isFromHistory; private string m_suggestion; + /// + /// True, if this item is from an older search query. + /// public bool IsFromHistory { get @@ -29,6 +35,9 @@ public bool IsFromHistory } } + /// + /// The label of this suggestion. + /// public string Suggestion { get @@ -44,6 +53,9 @@ public string Suggestion } } + /// + /// Creates a new . + /// public SearchSuggestionItem() { m_isFromHistory = false; diff --git a/MaterialDesignExtensions/Model/SearchSuggestionSource.cs b/MaterialDesignExtensions/Model/SearchSuggestionSource.cs deleted file mode 100644 index 8243c803..00000000 --- a/MaterialDesignExtensions/Model/SearchSuggestionSource.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace MaterialDesignExtensions.Model -{ - public interface ISearchSuggestionsSource - { - /// - /// Returns historical search suggestions for an empty and focused search. - /// - /// - IList GetSearchSuggestions(); - - /// - /// Returns suggestions based on auto-completion. - /// - /// - /// - IList GetAutoCompletion(string searchTerm); - } - - public class SearchSuggestionsSource : ISearchSuggestionsSource - { - public SearchSuggestionsSource() { } - - public IList GetAutoCompletion(string searchTerm) - { - return null; - } - - public IList GetSearchSuggestions() - { - return null; - } - } -} diff --git a/MaterialDesignExtensions/Model/SearchSuggestionsSource.cs b/MaterialDesignExtensions/Model/SearchSuggestionsSource.cs index 764a6de6..f06c8c6c 100644 --- a/MaterialDesignExtensions/Model/SearchSuggestionsSource.cs +++ b/MaterialDesignExtensions/Model/SearchSuggestionsSource.cs @@ -47,7 +47,6 @@ public IList GetAutoCompletion(string searchTerm) /// /// Returns suggestions based on auto-completion. /// - /// /// public IList GetSearchSuggestions() { diff --git a/MaterialDesignExtensions/Model/SecondLevelNavigationItem.cs b/MaterialDesignExtensions/Model/SecondLevelNavigationItem.cs index c445c9bf..8044afc6 100644 --- a/MaterialDesignExtensions/Model/SecondLevelNavigationItem.cs +++ b/MaterialDesignExtensions/Model/SecondLevelNavigationItem.cs @@ -6,8 +6,14 @@ namespace MaterialDesignExtensions.Model { + /// + /// A second level navigation item of a . + /// public class SecondLevelNavigationItem : NavigationItem { + /// + /// Creates a new . + /// public SecondLevelNavigationItem() : base() { } } } diff --git a/MaterialDesignExtensions/Model/SpecialDirectory.cs b/MaterialDesignExtensions/Model/SpecialDirectory.cs index c6b29b8e..602eaf28 100644 --- a/MaterialDesignExtensions/Model/SpecialDirectory.cs +++ b/MaterialDesignExtensions/Model/SpecialDirectory.cs @@ -7,16 +7,33 @@ using MaterialDesignThemes.Wpf; +// use Pri.LongPath classes instead of System.IO for the MaterialDesignExtensions.LongPath build to support long file system paths on older Windows and .NET versions +#if LONG_PATH +using DirectoryInfo = Pri.LongPath.DirectoryInfo; +#endif + namespace MaterialDesignExtensions.Model { + /// + /// A helper class to provide special locations (user folders etc.) inside the file system controls. + /// public class SpecialDirectory { private string m_label; + /// + /// The icon on the user inferface for this special location. + /// public PackIconKind Icon { get; set; } + /// + /// The object with the information for this special location. + /// public DirectoryInfo Info { get; set; } + /// + /// The label the user inferface for this special location. + /// public string Label { get @@ -37,6 +54,9 @@ public string Label } } + /// + /// Creates a new . + /// public SpecialDirectory() { } } } diff --git a/MaterialDesignExtensions/Model/SpecialDrive.cs b/MaterialDesignExtensions/Model/SpecialDrive.cs index a3c77942..0f96371c 100644 --- a/MaterialDesignExtensions/Model/SpecialDrive.cs +++ b/MaterialDesignExtensions/Model/SpecialDrive.cs @@ -9,14 +9,31 @@ namespace MaterialDesignExtensions.Model { + /// + /// A helper class to provide drives inside the file system controls. + /// public class SpecialDrive { + /// + /// The icon on the user inferface for this special location. + /// public PackIconKind Icon { get; set; } + /// + /// /// + /// The label the user inferface for this special location. + /// + /// public string Label { get; set; } + /// + /// The object with the information for this special location. + /// public DriveInfo Info { get; set; } + /// + /// Creates a new . + /// public SpecialDrive() { } } } diff --git a/MaterialDesignExtensions/Model/Step.cs b/MaterialDesignExtensions/Model/Step.cs index 03a0cfb4..8f629638 100644 --- a/MaterialDesignExtensions/Model/Step.cs +++ b/MaterialDesignExtensions/Model/Step.cs @@ -4,11 +4,12 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Windows; namespace MaterialDesignExtensions.Model { /// - /// Represents a step inside a . + /// Represents a step inside a . /// public interface IStep : INotifyPropertyChanged { @@ -27,6 +28,11 @@ public interface IStep : INotifyPropertyChanged /// object Header { get; set; } + /// + /// An optional template for icon of the step header. + /// + DataTemplate IconTemplate { get; set; } + /// /// Validates this step. /// Inherited classes may implement this method. @@ -43,6 +49,7 @@ public class Step : IStep public event PropertyChangedEventHandler PropertyChanged; protected object _header; + protected DataTemplate _iconTemplate; protected object _content; protected bool _hasValidationErrors; @@ -100,9 +107,28 @@ public virtual object Header } } + /// + /// An optional template for icon of the step header. + /// + public virtual DataTemplate IconTemplate + { + get + { + return _iconTemplate; + } + + set + { + _iconTemplate = value; + + OnPropertyChanged(nameof(IconTemplate)); + } + } + public Step() { _header = null; + _iconTemplate = null; _content = null; _hasValidationErrors = false; } diff --git a/MaterialDesignExtensions/Model/StepTitleHeader.cs b/MaterialDesignExtensions/Model/StepTitleHeader.cs index c3bef27e..d74d84d0 100644 --- a/MaterialDesignExtensions/Model/StepTitleHeader.cs +++ b/MaterialDesignExtensions/Model/StepTitleHeader.cs @@ -57,6 +57,9 @@ public string SecondLevelTitle } } + /// + /// Creates a new . + /// public StepTitleHeader() { m_firstLevelTitle = null; diff --git a/MaterialDesignExtensions/Model/SubheaderNavigationItem.cs b/MaterialDesignExtensions/Model/SubheaderNavigationItem.cs index c5540aa8..260d22fd 100644 --- a/MaterialDesignExtensions/Model/SubheaderNavigationItem.cs +++ b/MaterialDesignExtensions/Model/SubheaderNavigationItem.cs @@ -6,10 +6,16 @@ namespace MaterialDesignExtensions.Model { + /// + /// A header item of a . + /// public class SubheaderNavigationItem : NotSelectableNavigationItem { private string m_subheader; + /// + /// The label of this header. + /// public string Subheader { get @@ -25,6 +31,9 @@ public string Subheader } } + /// + /// Creates a new . + /// public SubheaderNavigationItem() : base() { diff --git a/MaterialDesignExtensions/Properties/AssemblyInfo.cs b/MaterialDesignExtensions/Properties/AssemblyInfo.cs index 00641604..92e9834d 100644 --- a/MaterialDesignExtensions/Properties/AssemblyInfo.cs +++ b/MaterialDesignExtensions/Properties/AssemblyInfo.cs @@ -3,18 +3,19 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Windows; +using System.Windows.Markup; // Allgemeine Informationen über eine Assembly werden über die folgenden // Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, // die einer Assembly zugeordnet sind. -[assembly: AssemblyTitle("Material Design Extensions")] +/*[assembly: AssemblyTitle("Material Design Extensions")] [assembly: AssemblyDescription("Material Design Extensions is based on Material Design in XAML Toolkit to provide additional controls and features for WPF apps")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Philipp Spiegel")] [assembly: AssemblyProduct("Material Design Extensions")] -[assembly: AssemblyCopyright("Copyright © 2017-2018")] +[assembly: AssemblyCopyright("Copyright © 2017-2019")] [assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] +[assembly: AssemblyCulture("")]*/ // Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar // für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von @@ -51,5 +52,10 @@ // Sie können alle Werte angeben oder Standardwerte für die Build- und Revisionsnummern verwenden, // übernehmen, indem Sie "*" eingeben: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("2.4.0.0")] -[assembly: AssemblyFileVersion("2.4.0.0")] +/*[assembly: AssemblyVersion("3.0.0.0")] +[assembly: AssemblyFileVersion("3.0.0.0")]*/ + +[assembly: XmlnsPrefix("https://spiegelp.github.io/MaterialDesignExtensions/winfx/xaml", "mde")] +[assembly: XmlnsDefinition("https://spiegelp.github.io/MaterialDesignExtensions/winfx/xaml", "MaterialDesignExtensions.Controls")] +[assembly: XmlnsDefinition("https://spiegelp.github.io/MaterialDesignExtensions/winfx/xaml", "MaterialDesignExtensions.Converters")] +[assembly: XmlnsDefinition("https://spiegelp.github.io/MaterialDesignExtensions/winfx/xaml", "MaterialDesignExtensions.Model")] diff --git a/MaterialDesignExtensions/TemplateSelectors/FileSystemEntryInfoTemplateSelector.cs b/MaterialDesignExtensions/TemplateSelectors/FileSystemEntryInfoTemplateSelector.cs index 66b474b9..ce784540 100644 --- a/MaterialDesignExtensions/TemplateSelectors/FileSystemEntryInfoTemplateSelector.cs +++ b/MaterialDesignExtensions/TemplateSelectors/FileSystemEntryInfoTemplateSelector.cs @@ -7,6 +7,12 @@ using System.Windows; using System.Windows.Controls; +// use Pri.LongPath classes instead of System.IO for the MaterialDesignExtensions.LongPath build to support long file system paths on older Windows and .NET versions +#if LONG_PATH +using DirectoryInfo = Pri.LongPath.DirectoryInfo; +using FileInfo = Pri.LongPath.FileInfo; +#endif + namespace MaterialDesignExtensions.TemplateSelectors { internal class FileSystemEntryInfoTemplateSelector : DataTemplateSelector diff --git a/MaterialDesignExtensions/TemplateSelectors/FileSystemEntryItemTemplateSelector.cs b/MaterialDesignExtensions/TemplateSelectors/FileSystemEntryItemTemplateSelector.cs index 128389d1..fcadb892 100644 --- a/MaterialDesignExtensions/TemplateSelectors/FileSystemEntryItemTemplateSelector.cs +++ b/MaterialDesignExtensions/TemplateSelectors/FileSystemEntryItemTemplateSelector.cs @@ -12,7 +12,13 @@ namespace MaterialDesignExtensions.TemplateSelectors { internal class FileSystemEntryItemTemplateSelector : DataTemplateSelector { - public FileSystemEntryItemTemplateSelector() : base() { } + public bool ForMultipleSelection { get; set; } + + public FileSystemEntryItemTemplateSelector() + : base() + { + ForMultipleSelection = false; + } public override DataTemplate SelectTemplate(object item, DependencyObject container) { @@ -24,7 +30,14 @@ public override DataTemplate SelectTemplate(object item, DependencyObject contai } else if (item is FileInfoItem) { - return element.FindResource("fileInfoItemTemplate") as DataTemplate; + if (ForMultipleSelection) + { + return element.FindResource("fileInfoItemMultipleTemplate") as DataTemplate; + } + else + { + return element.FindResource("fileInfoItemTemplate") as DataTemplate; + } } else if (item is FileSystemEntriesGroupHeader) { diff --git a/MaterialDesignExtensions/TemplateSelectors/StepIconTemplateSelector.cs b/MaterialDesignExtensions/TemplateSelectors/StepIconTemplateSelector.cs new file mode 100644 index 00000000..e39e19ee --- /dev/null +++ b/MaterialDesignExtensions/TemplateSelectors/StepIconTemplateSelector.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; + +using MaterialDesignExtensions.Controls; +using MaterialDesignExtensions.Model; + +namespace MaterialDesignExtensions.TemplateSelectors +{ + /// + /// A template selector for selecting an item for a step. + /// + public class StepIconTemplateSelector : DataTemplateSelector + { + /// + /// The stepper with the steps to apply this icon template selector on. + /// + public IStepper Stepper { get; set; } + + /// + /// Creates a new . + /// + public StepIconTemplateSelector() : this(null) { } + + /// + /// Creates a new . + /// + /// + public StepIconTemplateSelector(IStepper stepper) + { + Stepper = stepper; + } + + /// + /// Selects a template for the icon of the specified step. + /// + /// + /// + /// + public override DataTemplate SelectTemplate(object item, DependencyObject container) + { + if (Stepper != null && item is StepperStepViewModel stepViewModel && container is FrameworkElement element) + { + return SelectTemplate(Stepper, element, stepViewModel); + } + else + { + return base.SelectTemplate(item, container); + } + } + + /// + /// Selects a template for the icon of the specified step. + /// + /// + /// + /// + /// + public DataTemplate SelectTemplate(IStepper stepper, FrameworkElement element, StepperStepViewModel stepViewModel) + { + if (stepper != null && stepViewModel != null && element != null) + { + if (stepViewModel.Step.IconTemplate != null) + { + return stepViewModel.Step.IconTemplate; + } + else if (stepViewModel.Step.HasValidationErrors && stepper.ValidationErrorIconTemplate != null) + { + return stepper.ValidationErrorIconTemplate; + } + else if (stepper.Controller.ActiveStepViewModel != null && stepper.Controller.ActiveStepViewModel.Number > stepViewModel.Number + && stepper.DoneIconTemplate != null) + { + return stepper.DoneIconTemplate; + } else { + return element.FindResource("MaterialDesignStepNumberIconTemplate") as DataTemplate; + } + } + else + { + return base.SelectTemplate(stepViewModel, element); + } + } + } +} diff --git a/MaterialDesignExtensions/Themes/AppBarTemplates.xaml b/MaterialDesignExtensions/Themes/AppBarTemplates.xaml index 5b1f85be..4e82ba21 100644 --- a/MaterialDesignExtensions/Themes/AppBarTemplates.xaml +++ b/MaterialDesignExtensions/Themes/AppBarTemplates.xaml @@ -1,11 +1,14 @@  + + + + @@ -31,7 +34,7 @@ - + @@ -77,8 +80,7 @@ - - + @@ -103,9 +105,10 @@ @@ -130,21 +133,14 @@ - - - - - - - - + + - @@ -172,7 +168,7 @@ - + diff --git a/MaterialDesignExtensions/Themes/AutocompleteTemplates.xaml b/MaterialDesignExtensions/Themes/AutocompleteTemplates.xaml index 964394c7..bc57bc7a 100644 --- a/MaterialDesignExtensions/Themes/AutocompleteTemplates.xaml +++ b/MaterialDesignExtensions/Themes/AutocompleteTemplates.xaml @@ -1,14 +1,16 @@  + xmlns:converters="clr-namespace:MaterialDesignExtensions.Converters" + xmlns:internalCommands="clr-namespace:MaterialDesignExtensions.Commands.Internal"> + + - + @@ -23,7 +25,7 @@ - + @@ -70,7 +72,7 @@ - - - + + @@ -96,7 +98,7 @@ - - - + @@ -187,15 +189,15 @@ @@ -469,23 +518,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -551,72 +922,150 @@ Content="{Binding Path=Image}" ContentTemplateSelector="{StaticResource fileSystemEntryItemIconTemplateSelector}" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MaterialDesignExtensions/Themes/MessageDialogTemplates.xaml b/MaterialDesignExtensions/Themes/MessageDialogTemplates.xaml new file mode 100644 index 00000000..47b857d2 --- /dev/null +++ b/MaterialDesignExtensions/Themes/MessageDialogTemplates.xaml @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MaterialDesignExtensions/Themes/OversizedNumberSpinnerTemplate.xaml b/MaterialDesignExtensions/Themes/OversizedNumberSpinnerTemplate.xaml index c6b7c73a..21e2595b 100644 --- a/MaterialDesignExtensions/Themes/OversizedNumberSpinnerTemplate.xaml +++ b/MaterialDesignExtensions/Themes/OversizedNumberSpinnerTemplate.xaml @@ -1,9 +1,11 @@  + xmlns:controls="clr-namespace:MaterialDesignExtensions.Controls" + xmlns:internalCommands="clr-namespace:MaterialDesignExtensions.Commands.Internal"> + @@ -123,15 +125,7 @@ - @@ -214,147 +225,17 @@ - - + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + @@ -580,7 +618,7 @@ - + @@ -591,17 +629,15 @@ - + + + + diff --git a/MaterialDesignExtensionsDemo/Controls/AutocompleteControl.xaml b/MaterialDesignExtensionsDemo/Controls/AutocompleteControl.xaml index 8b8a9cd1..66403b72 100644 --- a/MaterialDesignExtensionsDemo/Controls/AutocompleteControl.xaml +++ b/MaterialDesignExtensionsDemo/Controls/AutocompleteControl.xaml @@ -4,24 +4,23 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:MaterialDesignExtensionsDemo.Controls" - xmlns:wpfLib="http://materialdesigninxaml.net/winfx/xaml/themes" - xmlns:controls="clr-namespace:MaterialDesignExtensions.Controls;assembly=MaterialDesignExtensions" - xmlns:converters="clr-namespace:MaterialDesignExtensions.Converters;assembly=MaterialDesignExtensions" + xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes" + xmlns:mde="https://spiegelp.github.io/MaterialDesignExtensions/winfx/xaml" mc:Ignorable="d" - d:DesignHeight="450" d:DesignWidth="700"> + d:DesignHeight="450" d:DesignWidth="700" Background="{DynamicResource MaterialDesignBackground}"> - + - + - + @@ -30,22 +29,24 @@ - - + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MaterialDesignExtensionsDemo/Controls/NavigationRailControl.xaml.cs b/MaterialDesignExtensionsDemo/Controls/NavigationRailControl.xaml.cs new file mode 100644 index 00000000..dbec7fde --- /dev/null +++ b/MaterialDesignExtensionsDemo/Controls/NavigationRailControl.xaml.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +using MaterialDesignExtensions.Model; + +namespace MaterialDesignExtensionsDemo.Controls +{ + public partial class NavigationRailControl : UserControl + { + public NavigationRailControl() + { + InitializeComponent(); + } + } +} diff --git a/MaterialDesignExtensionsDemo/Controls/OpenDirectoryControlControl.xaml b/MaterialDesignExtensionsDemo/Controls/OpenDirectoryControlControl.xaml index 2ff0cca0..1c84bcec 100644 --- a/MaterialDesignExtensionsDemo/Controls/OpenDirectoryControlControl.xaml +++ b/MaterialDesignExtensionsDemo/Controls/OpenDirectoryControlControl.xaml @@ -4,9 +4,9 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:MaterialDesignExtensionsDemo.Controls" - xmlns:controls="clr-namespace:MaterialDesignExtensions.Controls;assembly=MaterialDesignExtensions" + xmlns:mde="https://spiegelp.github.io/MaterialDesignExtensions/winfx/xaml" mc:Ignorable="d" - d:DesignHeight="500" d:DesignWidth="700"> + d:DesignHeight="500" d:DesignWidth="700" Background="{DynamicResource MaterialDesignBackground}"> @@ -19,10 +19,16 @@ + + + + - + diff --git a/MaterialDesignExtensionsDemo/Controls/OpenFileControlControl.xaml b/MaterialDesignExtensionsDemo/Controls/OpenFileControlControl.xaml index 4dfa1c5f..2114d8a1 100644 --- a/MaterialDesignExtensionsDemo/Controls/OpenFileControlControl.xaml +++ b/MaterialDesignExtensionsDemo/Controls/OpenFileControlControl.xaml @@ -4,9 +4,9 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:MaterialDesignExtensionsDemo.Controls" - xmlns:controls="clr-namespace:MaterialDesignExtensions.Controls;assembly=MaterialDesignExtensions" + xmlns:mde="https://spiegelp.github.io/MaterialDesignExtensions/winfx/xaml" mc:Ignorable="d" - d:DesignHeight="500" d:DesignWidth="700"> + d:DesignHeight="500" d:DesignWidth="700" Background="{DynamicResource MaterialDesignBackground}"> @@ -19,11 +19,14 @@ + + - + diff --git a/MaterialDesignExtensionsDemo/Controls/OpenMultipleDirectoriesControlControl.xaml b/MaterialDesignExtensionsDemo/Controls/OpenMultipleDirectoriesControlControl.xaml new file mode 100644 index 00000000..e57c7d20 --- /dev/null +++ b/MaterialDesignExtensionsDemo/Controls/OpenMultipleDirectoriesControlControl.xaml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/MaterialDesignExtensionsDemo/Controls/OpenMultipleDirectoriesControlControl.xaml.cs b/MaterialDesignExtensionsDemo/Controls/OpenMultipleDirectoriesControlControl.xaml.cs new file mode 100644 index 00000000..ee0b2a43 --- /dev/null +++ b/MaterialDesignExtensionsDemo/Controls/OpenMultipleDirectoriesControlControl.xaml.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +using MaterialDesignExtensions.Controls; + +using MaterialDesignExtensionsDemo.ViewModel; + +namespace MaterialDesignExtensionsDemo.Controls +{ + public partial class OpenMultipleDirectoriesControlControl : UserControl + { + public OpenMultipleDirectoriesControlControl() + { + InitializeComponent(); + } + + private void OpenMultipleDirectoriesControl_DirectoriesSelected(object sender, RoutedEventArgs args) + { + if (args is DirectoriesSelectedEventArgs eventArgs && DataContext is OpenMultipleDirectoriesControlViewModel viewModel) + { + StringBuilder sb = new StringBuilder("Selected directories: "); + eventArgs.Directories.ForEach(directory => sb.Append($"{directory}; ")); + + viewModel.SelectedAction = sb.ToString(); + } + } + + private void OpenMultipleDirectoriesControl_Cancel(object sender, RoutedEventArgs args) + { + if (DataContext is OpenMultipleDirectoriesControlViewModel viewModel) + { + viewModel.SelectedAction = "Cancel open directories"; + } + } + } +} diff --git a/MaterialDesignExtensionsDemo/Controls/OpenMultipleFilesControlControl.xaml b/MaterialDesignExtensionsDemo/Controls/OpenMultipleFilesControlControl.xaml new file mode 100644 index 00000000..35b6294f --- /dev/null +++ b/MaterialDesignExtensionsDemo/Controls/OpenMultipleFilesControlControl.xaml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + diff --git a/MaterialDesignExtensionsDemo/Controls/OpenMultipleFilesControlControl.xaml.cs b/MaterialDesignExtensionsDemo/Controls/OpenMultipleFilesControlControl.xaml.cs new file mode 100644 index 00000000..fdb9b605 --- /dev/null +++ b/MaterialDesignExtensionsDemo/Controls/OpenMultipleFilesControlControl.xaml.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +using MaterialDesignExtensions.Controls; + +using MaterialDesignExtensionsDemo.ViewModel; + +namespace MaterialDesignExtensionsDemo.Controls +{ + public partial class OpenMultipleFilesControlControl : UserControl + { + public OpenMultipleFilesControlControl() + { + InitializeComponent(); + } + + private void OpenMultipleFilesControl_DirectoriesSelected(object sender, RoutedEventArgs args) + { + if (args is FilesSelectedEventArgs eventArgs && DataContext is OpenMultipleFilesControlViewModel viewModel) + { + StringBuilder sb = new StringBuilder("Selected files: "); + eventArgs.Files.ForEach(file => sb.Append($"{file}; ")); + + viewModel.SelectedAction = sb.ToString(); + } + } + + private void OpenMultipleFilesControl_Cancel(object sender, RoutedEventArgs args) + { + if (DataContext is OpenMultipleFilesControlViewModel viewModel) + { + viewModel.SelectedAction = "Cancel open files"; + } + } + } +} diff --git a/MaterialDesignExtensionsDemo/Controls/OversizedNumberSpinnerControl.xaml b/MaterialDesignExtensionsDemo/Controls/OversizedNumberSpinnerControl.xaml index 11a6a359..54ace898 100644 --- a/MaterialDesignExtensionsDemo/Controls/OversizedNumberSpinnerControl.xaml +++ b/MaterialDesignExtensionsDemo/Controls/OversizedNumberSpinnerControl.xaml @@ -4,9 +4,9 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:MaterialDesignExtensionsDemo.Controls" - xmlns:controls="clr-namespace:MaterialDesignExtensions.Controls;assembly=MaterialDesignExtensions" + xmlns:mde="https://spiegelp.github.io/MaterialDesignExtensions/winfx/xaml" mc:Ignorable="d" - d:DesignHeight="300" d:DesignWidth="500" Background="Transparent"> + d:DesignHeight="300" d:DesignWidth="500" Background="{DynamicResource MaterialDesignBackground}"> @@ -15,8 +15,8 @@ - - + + diff --git a/MaterialDesignExtensionsDemo/Controls/SaveFileControlControl.xaml b/MaterialDesignExtensionsDemo/Controls/SaveFileControlControl.xaml index beaba8b6..d25289cb 100644 --- a/MaterialDesignExtensionsDemo/Controls/SaveFileControlControl.xaml +++ b/MaterialDesignExtensionsDemo/Controls/SaveFileControlControl.xaml @@ -4,9 +4,9 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:MaterialDesignExtensionsDemo.Controls" - xmlns:controls="clr-namespace:MaterialDesignExtensions.Controls;assembly=MaterialDesignExtensions" + xmlns:mde="https://spiegelp.github.io/MaterialDesignExtensions/winfx/xaml" mc:Ignorable="d" - d:DesignHeight="500" d:DesignWidth="700"> + d:DesignHeight="500" d:DesignWidth="700" Background="{DynamicResource MaterialDesignBackground}"> @@ -16,14 +16,20 @@ - + - + + + + + - + diff --git a/MaterialDesignExtensionsDemo/Controls/SearchControl.xaml b/MaterialDesignExtensionsDemo/Controls/SearchControl.xaml index f59da099..28b0509e 100644 --- a/MaterialDesignExtensionsDemo/Controls/SearchControl.xaml +++ b/MaterialDesignExtensionsDemo/Controls/SearchControl.xaml @@ -1,23 +1,38 @@  + d:DesignHeight="450" d:DesignWidth="700" Background="{DynamicResource MaterialDesignBackground}"> - + - + + + + + + + + + + + + + + + diff --git a/MaterialDesignExtensionsDemo/Controls/SearchControl.xaml.cs b/MaterialDesignExtensionsDemo/Controls/SearchControl.xaml.cs index f8b21800..13b1bdf1 100644 --- a/MaterialDesignExtensionsDemo/Controls/SearchControl.xaml.cs +++ b/MaterialDesignExtensionsDemo/Controls/SearchControl.xaml.cs @@ -28,5 +28,15 @@ private void SearchHandler1(object sender, SearchEventArgs args) { searchResultTextBlock1.Text = "Your are looking for '" + args.SearchTerm + "'."; } + + private void SearchHandler2(object sender, SearchEventArgs args) + { + searchResultTextBlock2.Text = "Your are looking for '" + args.SearchTerm + "'."; + } + + private void SearchHandler3(object sender, SearchEventArgs args) + { + searchResultTextBlock3.Text = "Your are looking for '" + args.SearchTerm + "'."; + } } } diff --git a/MaterialDesignExtensionsDemo/Controls/StepperControl.xaml b/MaterialDesignExtensionsDemo/Controls/StepperControl.xaml index c87908ac..9d980d1d 100644 --- a/MaterialDesignExtensionsDemo/Controls/StepperControl.xaml +++ b/MaterialDesignExtensionsDemo/Controls/StepperControl.xaml @@ -5,21 +5,19 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:MaterialDesignExtensionsDemo.Controls" xmlns:viewModel="clr-namespace:MaterialDesignExtensionsDemo.ViewModel" - xmlns:controls="clr-namespace:MaterialDesignExtensions.Controls;assembly=MaterialDesignExtensions" - xmlns:converters="clr-namespace:MaterialDesignExtensions.Converters;assembly=MaterialDesignExtensions" - xmlns:model="clr-namespace:MaterialDesignExtensions.Model;assembly=MaterialDesignExtensions" + xmlns:mde="https://spiegelp.github.io/MaterialDesignExtensions/winfx/xaml" mc:Ignorable="d" - d:DesignHeight="300" d:DesignWidth="500" Background="Transparent"> + d:DesignHeight="300" d:DesignWidth="500" Background="{DynamicResource MaterialDesignBackground}"> - + - + @@ -28,7 +26,7 @@ - + @@ -57,11 +55,11 @@ - +