diff options
author | Tiago Conceição <Tiago_caza@hotmail.com> | 2021-04-18 02:18:42 +0300 |
---|---|---|
committer | Tiago Conceição <Tiago_caza@hotmail.com> | 2021-04-18 02:18:42 +0300 |
commit | 6cfedea4cc36200caab87301e0dd121e97ddc8f2 (patch) | |
tree | 3325980560fc5bb3fa8befea1b827a2ca945e9ea /UVtools.WPF | |
parent | 4dae750e83987fde8b34087d26fc75407e88f55a (diff) |
v2.9.1v2.9.1
* **File formats:**
* PhotonS: Implement the write/encode method to allow to use this format and fix the thumbnail
* VDT: Allow to auto convert the .vdt to the target printer format using the Machine - Notes, using a flag: FILEFORMAT_YourPrinterExtension, for example: FILEFORMAT_CTB
* (Fix) Unable to convert files with no thumbnails to other file format that requires thumbnails
* **Tools:**
* (Add) Re-height: Option to Anti-Aliasing layers
* (Fix) Morph and Blur: The combobox was not setting to the selected item when preform a redo operation (Ctrl+Shift+Z)
* **GUI:**
* (Change) Progress window to be a grid element inside MainWindow, this allow to reuse the graphics and its elements without the need of spawning a Window instance everytime a progress is shown, resulting in better performance and more fluid transaction
* (Improvement) Clear issues when generating calibration tests
Diffstat (limited to 'UVtools.WPF')
22 files changed, 362 insertions, 216 deletions
diff --git a/UVtools.WPF/App.axaml b/UVtools.WPF/App.axaml index ede140d..6a486e4 100644 --- a/UVtools.WPF/App.axaml +++ b/UVtools.WPF/App.axaml @@ -1,6 +1,11 @@ <Application xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:converters="clr-namespace:UVtools.WPF.Converters" x:Class="UVtools.WPF.App"> + <Application.Resources> + <converters:EnumToCollectionConverter x:Key="EnumToCollectionConverter" /> + <converters:FromValueDescriptionToEnumConverter x:Key="FromValueDescriptionToEnumConverter" /> + </Application.Resources> <Application.Styles> <FluentTheme Mode="Light"/> <StyleInclude Source="avares://ThemeEditor.Controls.ColorPicker/ColorPicker.axaml"/> diff --git a/UVtools.WPF/Controls/Tools/ToolBlurControl.axaml b/UVtools.WPF/Controls/Tools/ToolBlurControl.axaml index e37b992..e2f466b 100644 --- a/UVtools.WPF/Controls/Tools/ToolBlurControl.axaml +++ b/UVtools.WPF/Controls/Tools/ToolBlurControl.axaml @@ -17,16 +17,15 @@ <ComboBox Grid.Column="2" HorizontalAlignment="Left" - SelectedIndex="{Binding SelectedAlgorithmIndex}" Width="450" HorizontalContentAlignment="Stretch" - Items="{Binding Operation.BlurTypes}" - /> + Items="{Binding Operation.BlurOperation, Converter={StaticResource EnumToCollectionConverter}, Mode=OneTime}" + SelectedItem="{Binding Operation.BlurOperation, Converter={StaticResource FromValueDescriptionToEnumConverter}}"/> <TextBlock VerticalAlignment="Center" Grid.Row="2" - IsEnabled="{Binding IsSizeEnabled}" + IsEnabled="{Binding Operation.IsSizeEnabled}" Text="Size:"/> <NumericUpDown Grid.Row="2" @@ -34,15 +33,13 @@ HorizontalAlignment="Left" Width="150" Minimum="1" - IsEnabled="{Binding IsSizeEnabled}" + IsEnabled="{Binding Operation.IsSizeEnabled}" Value="{Binding Operation.Size}" /> </Grid> - <uc:KernelControl - Name="KernelCtrl" - IsVisible="{Binding $parent[UserControl].IsKernelVisible}" - /> + <uc:KernelControl Name="KernelCtrl" + IsVisible="{Binding $parent[UserControl].DataContext.Operation.IsKernelVisible}"/> </StackPanel> diff --git a/UVtools.WPF/Controls/Tools/ToolBlurControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolBlurControl.axaml.cs index 89f6322..86e5c19 100644 --- a/UVtools.WPF/Controls/Tools/ToolBlurControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolBlurControl.axaml.cs @@ -7,37 +7,9 @@ namespace UVtools.WPF.Controls.Tools { public class ToolBlurControl : ToolControl { - private int _selectedAlgorithmIndex; - private bool _isSizeEnabled = true; - private bool _isKernelVisible; private KernelControl _kernelCtrl; public OperationBlur Operation => BaseOperation as OperationBlur; - public int SelectedAlgorithmIndex - { - get => _selectedAlgorithmIndex; - set - { - if(!RaiseAndSetIfChanged(ref _selectedAlgorithmIndex, value) || value < 0) return; - Operation.BlurOperation = (OperationBlur.BlurAlgorithm) OperationBlur.BlurTypes[_selectedAlgorithmIndex].Tag; - IsKernelVisible = Operation.BlurOperation == OperationBlur.BlurAlgorithm.Filter2D; - IsSizeEnabled = Operation.BlurOperation != OperationBlur.BlurAlgorithm.Pyramid && - Operation.BlurOperation != OperationBlur.BlurAlgorithm.Filter2D; - } - } - - public bool IsSizeEnabled - { - get => _isSizeEnabled; - set => RaiseAndSetIfChanged(ref _isSizeEnabled, value); - } - - public bool IsKernelVisible - { - get => _isKernelVisible; - set => RaiseAndSetIfChanged(ref _isKernelVisible, value); - } - public ToolBlurControl() { InitializeComponent(); @@ -54,17 +26,8 @@ namespace UVtools.WPF.Controls.Tools { Operation.Kernel.Matrix = _kernelCtrl.GetMatrix(); Operation.Kernel.Anchor = _kernelCtrl.Anchor; - return !(Operation.Kernel.Matrix is null); + return Operation.Kernel.Matrix is not null; } - public override void Callback(ToolWindow.Callbacks callback) - { - switch (callback) - { - case ToolWindow.Callbacks.ProfileLoaded: - SelectedAlgorithmIndex = Operation.BlurTypeIndex; - break; - } - } } } diff --git a/UVtools.WPF/Controls/Tools/ToolControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolControl.axaml.cs index e88db59..09dc732 100644 --- a/UVtools.WPF/Controls/Tools/ToolControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolControl.axaml.cs @@ -80,6 +80,6 @@ namespace UVtools.WPF.Controls.Tools /// </summary> /// <param name="text"></param> /// <returns></returns> - public async Task<bool> ValidateFormFromString(StringTag text) => await ValidateFormFromString(text?.ToString()); + public async Task<bool> ValidateFormFromString(ValueDescription text) => await ValidateFormFromString(text?.ToString()); } } diff --git a/UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml.cs index 0fc4755..7381dd6 100644 --- a/UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml.cs @@ -20,7 +20,7 @@ namespace UVtools.WPF.Controls.Tools public class ToolLayerImportControl : ToolControl { private bool _isAutoSortLayersByFileNameChecked; - private StringTag _selectedFile; + private ValueDescription _selectedFile; private Bitmap _previewImage; private ListBox FilesListBox; @@ -60,7 +60,7 @@ namespace UVtools.WPF.Controls.Tools } - public StringTag SelectedFile + public ValueDescription SelectedFile { get => _selectedFile; set @@ -71,12 +71,12 @@ namespace UVtools.WPF.Controls.Tools PreviewImage = null; return; } - if (!_selectedFile.TagString.EndsWith(".png", StringComparison.OrdinalIgnoreCase) && - !_selectedFile.TagString.EndsWith(".bmp", StringComparison.OrdinalIgnoreCase) && - !_selectedFile.TagString.EndsWith(".jpeg", StringComparison.OrdinalIgnoreCase) && - !_selectedFile.TagString.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase) && - !_selectedFile.TagString.EndsWith(".gif", StringComparison.OrdinalIgnoreCase)) return; - PreviewImage = new Bitmap(_selectedFile.TagString); + if (!_selectedFile.ValueAsString.EndsWith(".png", StringComparison.OrdinalIgnoreCase) && + !_selectedFile.ValueAsString.EndsWith(".bmp", StringComparison.OrdinalIgnoreCase) && + !_selectedFile.ValueAsString.EndsWith(".jpeg", StringComparison.OrdinalIgnoreCase) && + !_selectedFile.ValueAsString.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase) && + !_selectedFile.ValueAsString.EndsWith(".gif", StringComparison.OrdinalIgnoreCase)) return; + PreviewImage = new Bitmap(_selectedFile.ValueAsString); } } @@ -94,8 +94,8 @@ namespace UVtools.WPF.Controls.Tools FilesListBox = this.Find<ListBox>("FilesListBox"); FilesListBox.DoubleTapped += (sender, args) => { - if (!(FilesListBox.SelectedItem is StringTag file)) return; - App.StartProcess(file.TagString); + if (!(FilesListBox.SelectedItem is ValueDescription file)) return; + App.StartProcess(file.ValueAsString); }; FilesListBox.KeyUp += (sender, e) => { @@ -197,7 +197,7 @@ namespace UVtools.WPF.Controls.Tools public void RemoveFiles() { - Operation.Files.RemoveMany(FilesListBox.SelectedItems.OfType<StringTag>()); + Operation.Files.RemoveMany(FilesListBox.SelectedItems.OfType<ValueDescription>()); } public void ClearFiles() diff --git a/UVtools.WPF/Controls/Tools/ToolLayerReHeightControl.axaml b/UVtools.WPF/Controls/Tools/ToolLayerReHeightControl.axaml index 3db4d3c..168ac75 100644 --- a/UVtools.WPF/Controls/Tools/ToolLayerReHeightControl.axaml +++ b/UVtools.WPF/Controls/Tools/ToolLayerReHeightControl.axaml @@ -4,17 +4,28 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="UVtools.WPF.Controls.Tools.ToolLayerReHeightControl"> - <StackPanel Orientation="Vertical" Spacing="10"> + + + <StackPanel Orientation="Vertical" Spacing="10"> <TextBlock VerticalAlignment="Center" Text="{Binding CurrentLayers}"/> - <StackPanel Orientation="Horizontal" Spacing="10"> - <TextBlock VerticalAlignment="Center" Text="Modifier:"/> - <ComboBox - MinWidth="300" - SelectedItem="{Binding Operation.Item}" - Items="{Binding Presets}" + <Grid RowDefinitions="Auto,10,Auto" + ColumnDefinitions="Auto,10,Auto"> + + <TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Text="Modifier:"/> + <ComboBox + Grid.Row="0" Grid.Column="2" Width="500" + SelectedItem="{Binding Operation.SelectedItem}" + Items="{Binding Operation.Presets}" /> + + <TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Text="Anti-Aliasing:"/> + <ComboBox Grid.Row="2" Grid.Column="2" Width="500" + IsEnabled="{Binding Operation.CanAntiAliasing}" + Items="{Binding Operation.AntiAliasingType, Converter={StaticResource EnumToCollectionConverter}, Mode=OneTime}" + SelectedItem="{Binding Operation.AntiAliasingType, Converter={StaticResource FromValueDescriptionToEnumConverter}}"> + </ComboBox> + </Grid> + </StackPanel> - - </StackPanel> </UserControl> diff --git a/UVtools.WPF/Controls/Tools/ToolLayerReHeightControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolLayerReHeightControl.axaml.cs index 4b6e093..8a848b9 100644 --- a/UVtools.WPF/Controls/Tools/ToolLayerReHeightControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolLayerReHeightControl.axaml.cs @@ -8,27 +8,18 @@ namespace UVtools.WPF.Controls.Tools { public OperationLayerReHeight Operation => BaseOperation as OperationLayerReHeight; - public OperationLayerReHeight.OperationLayerReHeightItem[] Presets => App.SlicerFile is null ? null : OperationLayerReHeight.GetItems( - App.SlicerFile.LayerCount, - (decimal)App.SlicerFile.LayerHeight); - public string CurrentLayers => $"Current layers: {App.SlicerFile.LayerCount} at {App.SlicerFile.LayerHeight}mm"; public ToolLayerReHeightControl() { InitializeComponent(); BaseOperation = new OperationLayerReHeight(SlicerFile); - var presets = Presets; - if (presets is null || presets.Length == 0) + if (Operation.SelectedItem is null) { App.MainWindow.MessageBoxInfo("No valid configuration to be able to re-height.\n" + "As workaround clone first or last layer and try re run this tool.", "Not possible to re-height"); CanRun = false; } - else - { - Operation.Item = presets[0]; - } } private void InitializeComponent() diff --git a/UVtools.WPF/Controls/Tools/ToolMorphControl.axaml b/UVtools.WPF/Controls/Tools/ToolMorphControl.axaml index 5611261..868a47c 100644 --- a/UVtools.WPF/Controls/Tools/ToolMorphControl.axaml +++ b/UVtools.WPF/Controls/Tools/ToolMorphControl.axaml @@ -56,8 +56,8 @@ Grid.Row="2" Grid.Column="2" HorizontalAlignment="Stretch" - SelectedIndex="{Binding MorphSelectedIndex}" - Items="{Binding Operation.MorphOperations}" + Items="{Binding Operation.MorphOperation, Converter={StaticResource EnumToCollectionConverter}, Mode=OneTime}" + SelectedItem="{Binding Operation.MorphOperation, Converter={StaticResource FromValueDescriptionToEnumConverter}}" /> </Grid> diff --git a/UVtools.WPF/Controls/Tools/ToolMorphControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolMorphControl.axaml.cs index 9d17f05..b3ca26d 100644 --- a/UVtools.WPF/Controls/Tools/ToolMorphControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolMorphControl.axaml.cs @@ -1,30 +1,15 @@ -using System; -using System.Diagnostics; -using Avalonia.Controls; +using Avalonia.Controls; using Avalonia.Markup.Xaml; -using Emgu.CV.CvEnum; using UVtools.Core.Operations; -using UVtools.WPF.Windows; namespace UVtools.WPF.Controls.Tools { public class ToolMorphControl : ToolControl { - private byte _morphSelectedIndex; public OperationMorph Operation => BaseOperation as OperationMorph; private KernelControl _kernelCtrl; - public byte MorphSelectedIndex - { - get => _morphSelectedIndex; - set - { - if(!RaiseAndSetIfChanged(ref _morphSelectedIndex, value)) return; - Operation.MorphOperation = (MorphOp)OperationMorph.MorphOperations[_morphSelectedIndex].Tag; - } - } - public ToolMorphControl() { InitializeComponent(); @@ -41,17 +26,7 @@ namespace UVtools.WPF.Controls.Tools { Operation.Kernel.Matrix = _kernelCtrl.GetMatrix(); Operation.Kernel.Anchor = _kernelCtrl.Anchor; - return !(Operation.Kernel.Matrix is null); - } - - public override void Callback(ToolWindow.Callbacks callback) - { - switch (callback) - { - case ToolWindow.Callbacks.ProfileLoaded: - MorphSelectedIndex = Operation.MorphOperationIndex; - break; - } + return Operation.Kernel.Matrix is not null; } } } diff --git a/UVtools.WPF/Converters/EnumToCollectionConverter.cs b/UVtools.WPF/Converters/EnumToCollectionConverter.cs new file mode 100644 index 0000000..f585f86 --- /dev/null +++ b/UVtools.WPF/Converters/EnumToCollectionConverter.cs @@ -0,0 +1,29 @@ +/* + * GNU AFFERO GENERAL PUBLIC LICENSE + * Version 3, 19 November 2007 + * Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. + */ + +using System; +using Avalonia.Data.Converters; +using UVtools.Core.Extensions; + +namespace UVtools.WPF.Converters +{ + public class EnumToCollectionConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + return EnumExtensions.GetAllValuesAndDescriptions(value.GetType()); + } + + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + return null; + //string parameterString = parameter.ToString(); + //return Enum.Parse(targetType, parameterString); + } + } +} diff --git a/UVtools.WPF/Converters/FromValueDescriptionToEnumConverter.cs b/UVtools.WPF/Converters/FromValueDescriptionToEnumConverter.cs new file mode 100644 index 0000000..e37fb58 --- /dev/null +++ b/UVtools.WPF/Converters/FromValueDescriptionToEnumConverter.cs @@ -0,0 +1,31 @@ +/* + * GNU AFFERO GENERAL PUBLIC LICENSE + * Version 3, 19 November 2007 + * Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. + */ + +using System; +using System.Linq; +using Avalonia.Data.Converters; +using UVtools.Core.Extensions; + +namespace UVtools.WPF.Converters +{ + public class FromValueDescriptionToEnumConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + var list = EnumExtensions.GetAllValuesAndDescriptions(value.GetType()); + return list.FirstOrDefault(vd => vd.Value.Equals(value)); + } + + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + if (value is null) return null; + var list = EnumExtensions.GetAllValuesAndDescriptions(targetType); + return list.FirstOrDefault(vd => vd.Description == value.ToString())?.Value; + } + } +} diff --git a/UVtools.WPF/Extensions/BitmapExtension.cs b/UVtools.WPF/Extensions/BitmapExtension.cs index 662f9d0..ae5f5ce 100644 --- a/UVtools.WPF/Extensions/BitmapExtension.cs +++ b/UVtools.WPF/Extensions/BitmapExtension.cs @@ -6,15 +6,12 @@ * of this license document, but changing it is not allowed. */ using System; -using System.Diagnostics; -using System.Runtime.InteropServices; using Avalonia; using Avalonia.Media.Imaging; using Avalonia.Platform; using Emgu.CV; using Emgu.CV.CvEnum; using SkiaSharp; -using UVtools.Core.Extensions; namespace UVtools.WPF.Extensions { diff --git a/UVtools.WPF/MainWindow.Information.cs b/UVtools.WPF/MainWindow.Information.cs index 3822b94..8b40065 100644 --- a/UVtools.WPF/MainWindow.Information.cs +++ b/UVtools.WPF/MainWindow.Information.cs @@ -31,15 +31,15 @@ namespace UVtools.WPF { public partial class MainWindow { - public ObservableCollection<SlicerProperty> SlicerProperties { get; } = new ObservableCollection<SlicerProperty>(); + public ObservableCollection<SlicerProperty> SlicerProperties { get; } = new(); public DataGrid PropertiesGrid; public DataGrid CurrentLayerGrid; private uint _visibleThumbnailIndex; private Bitmap _visibleThumbnailImage; - private ObservableCollection<StringTag> _currentLayerProperties = new ObservableCollection<StringTag>(); + private ObservableCollection<ValueDescription> _currentLayerProperties = new(); - public ObservableCollection<StringTag> CurrentLayerProperties + public ObservableCollection<ValueDescription> CurrentLayerProperties { get => _currentLayerProperties; set => RaiseAndSetIfChanged(ref _currentLayerProperties, value); @@ -336,38 +336,33 @@ namespace UVtools.WPF var layer = LayerCache.Layer; CurrentLayerProperties.Clear(); - CurrentLayerProperties.Add(new StringTag(nameof(layer.Index), $"{layer.Index}")); - CurrentLayerProperties.Add(new StringTag(nameof(layer.LayerHeight), $"{Layer.ShowHeight(layer.LayerHeight)}mm")); - //CurrentLayerProperties.Add(new KeyValuePair<string, string>(nameof(layer.Filename), layer.Filename)); - CurrentLayerProperties.Add(new StringTag(nameof(layer.PositionZ), $"{Layer.ShowHeight(layer.PositionZ)}mm")); - CurrentLayerProperties.Add(new StringTag(nameof(layer.IsBottomLayer), layer.IsBottomLayer.ToString())); - CurrentLayerProperties.Add(new StringTag(nameof(layer.IsModified), layer.IsModified.ToString())); - //CurrentLayerProperties.Add(new StringTag(nameof(layer.BoundingRectangle), layer.BoundingRectangle.ToString())); - //CurrentLayerProperties.Add(new StringTag(nameof(layer.NonZeroPixelCount), layer.NonZeroPixelCount.ToString())); - CurrentLayerProperties.Add(new StringTag(nameof(layer.ExposureTime), $"{layer.ExposureTime:F2}s")); + CurrentLayerProperties.Add(new ValueDescription($"{layer.Index}", nameof(layer.Index))); + CurrentLayerProperties.Add(new ValueDescription($"{Layer.ShowHeight(layer.LayerHeight)}mm", nameof(layer.LayerHeight))); + CurrentLayerProperties.Add(new ValueDescription($"{Layer.ShowHeight(layer.PositionZ)}mm", nameof(layer.PositionZ))); + CurrentLayerProperties.Add(new ValueDescription(layer.IsBottomLayer.ToString(), nameof(layer.IsBottomLayer))); + CurrentLayerProperties.Add(new ValueDescription(layer.IsModified.ToString(), nameof(layer.IsModified))); + CurrentLayerProperties.Add(new ValueDescription($"{layer.ExposureTime:F2}s", nameof(layer.ExposureTime))); if (SlicerFile.PrintParameterPerLayerModifiers is not null) { if (SlicerFile.PrintParameterPerLayerModifiers.Contains(FileFormat.PrintParameterModifier.LiftHeight)) - CurrentLayerProperties.Add(new StringTag(nameof(layer.LiftHeight), - $"{layer.LiftHeight.ToString(CultureInfo.InvariantCulture)}mm @ {layer.LiftSpeed.ToString(CultureInfo.InvariantCulture)}mm/min")); - //CurrentLayerProperties.Adnew StringTagg>(nameof(layer.LiftSpeed), $"{layer.LiftSpeed.ToString(CultureInfo.InvariantCulture)}mm/min")); + CurrentLayerProperties.Add(new ValueDescription($"{layer.LiftHeight.ToString(CultureInfo.InvariantCulture)}mm @ {layer.LiftSpeed.ToString(CultureInfo.InvariantCulture)}mm/min", nameof(layer.LiftHeight))); if (SlicerFile.PrintParameterPerLayerModifiers.Contains(FileFormat.PrintParameterModifier.RetractSpeed)) - CurrentLayerProperties.Add(new StringTag(nameof(layer.RetractSpeed), - $"{layer.RetractSpeed}mm/min")); + CurrentLayerProperties.Add(new ValueDescription($"{layer.RetractSpeed}mm/min", + nameof(layer.RetractSpeed))); if (SlicerFile.PrintParameterPerLayerModifiers.Contains(FileFormat.PrintParameterModifier.LightOffDelay)) - CurrentLayerProperties.Add(new StringTag(nameof(layer.LightOffDelay), - $"{layer.LightOffDelay}s")); + CurrentLayerProperties.Add(new ValueDescription($"{layer.LightOffDelay}s", + nameof(layer.LightOffDelay))); if (SlicerFile.PrintParameterPerLayerModifiers.Contains(FileFormat.PrintParameterModifier.LightPWM)) - CurrentLayerProperties.Add(new StringTag(nameof(layer.LightPWM), layer.LightPWM.ToString())); + CurrentLayerProperties.Add(new ValueDescription(layer.LightPWM.ToString(), nameof(layer.LightPWM))); } var materialMillilitersPercent = layer.MaterialMillilitersPercent; if (!float.IsNaN(materialMillilitersPercent)) { - CurrentLayerProperties.Add(new StringTag(nameof(layer.MaterialMilliliters), $"{layer.MaterialMilliliters}ml ({materialMillilitersPercent:F2}%)")); + CurrentLayerProperties.Add(new ValueDescription($"{layer.MaterialMilliliters}ml ({materialMillilitersPercent:F2}%)", nameof(layer.MaterialMilliliters))); } } diff --git a/UVtools.WPF/MainWindow.Issues.cs b/UVtools.WPF/MainWindow.Issues.cs index 669b180..4d29d55 100644 --- a/UVtools.WPF/MainWindow.Issues.cs +++ b/UVtools.WPF/MainWindow.Issues.cs @@ -111,20 +111,19 @@ namespace UVtools.WPF IsGUIEnabled = false; + ShowProgressWindow("Removing selected issues", false); Clipboard.Snapshot(); var task = await Task.Factory.StartNew(() => { - ShowProgressWindow("Removing selected issues"); - var progress = ProgressWindow.RestartProgress(false); - progress.Reset("Removing selected issues", (uint)processIssues.Count); + Progress.Reset("Removing selected issues", (uint)processIssues.Count); bool result = false; try { Parallel.ForEach(processIssues, layerIssues => { - if (progress.Token.IsCancellationRequested) return; + if (Progress.Token.IsCancellationRequested) return; using (var image = SlicerFile[layerIssues.Key].LayerMat) { var bytes = image.GetPixelSpan<byte>(); @@ -162,7 +161,7 @@ namespace UVtools.WPF } } - progress.LockAndIncrement(); + Progress.LockAndIncrement(); }); if (layersRemove.Count > 0) @@ -275,6 +274,7 @@ namespace UVtools.WPF IsGUIEnabled = false; + ShowProgressWindow("Updating Issues"); var issueList = Issues.ToList(); @@ -291,12 +291,10 @@ namespace UVtools.WPF var resultIssues = await Task.Factory.StartNew(() => { - ShowProgressWindow("Updating Issues"); try { var issues = SlicerFile.LayerManager.GetAllIssues(islandConfig, overhangConfig, resinTrapConfig, - touchingBoundConfig, printHeightConfig, false, IgnoredIssues, - ProgressWindow.RestartProgress()); + touchingBoundConfig, printHeightConfig, false, IgnoredIssues, Progress); issues.RemoveAll(issue => issue.Type != LayerIssue.IssueType.Island && issue.Type != LayerIssue.IssueType.Overhang); // Remove all non islands and overhangs return issues; @@ -450,15 +448,14 @@ namespace UVtools.WPF Issues.Clear(); IsGUIEnabled = false; - + ShowProgressWindow("Computing Issues"); var resultIssues = await Task.Factory.StartNew(() => { - ShowProgressWindow("Computing Issues"); try { var issues = SlicerFile.LayerManager.GetAllIssues(islandConfig, overhangConfig, resinTrapConfig, touchingBoundConfig, - printHeightConfig, emptyLayersConfig, IgnoredIssues, ProgressWindow.RestartProgress()); + printHeightConfig, emptyLayersConfig, IgnoredIssues, Progress); return issues; } catch (OperationCanceledException) @@ -531,6 +528,13 @@ namespace UVtools.WPF } } + public void IssuesClear(bool clearIgnored = true) + { + Issues.Clear(); + if(clearIgnored) IgnoredIssues.Clear(); + UpdateLayerTrackerHighlightIssues(); + } + public IslandDetectionConfiguration GetIslandDetectionConfiguration(bool enable) { diff --git a/UVtools.WPF/MainWindow.PixelEditor.cs b/UVtools.WPF/MainWindow.PixelEditor.cs index e7984cd..1c4f0a4 100644 --- a/UVtools.WPF/MainWindow.PixelEditor.cs +++ b/UVtools.WPF/MainWindow.PixelEditor.cs @@ -378,15 +378,14 @@ namespace UVtools.WPF if (result == ButtonResult.Yes) { IsGUIEnabled = false; - + ShowProgressWindow("Drawing pixels"); Clipboard.Snapshot(); var task = await Task.Factory.StartNew(async () => { - ShowProgressWindow("Drawing pixels"); try { - SlicerFile.LayerManager.DrawModifications(Drawings, ProgressWindow.RestartProgress()); + SlicerFile.LayerManager.DrawModifications(Drawings, Progress); return true; } catch (OperationCanceledException) diff --git a/UVtools.WPF/MainWindow.Progress.cs b/UVtools.WPF/MainWindow.Progress.cs new file mode 100644 index 0000000..6f2d304 --- /dev/null +++ b/UVtools.WPF/MainWindow.Progress.cs @@ -0,0 +1,95 @@ +/* + * GNU AFFERO GENERAL PUBLIC LICENSE + * Version 3, 19 November 2007 + * Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. + */ + +using System; +using System.Timers; +using Avalonia.Threading; +using UVtools.Core.Operations; +using UVtools.WPF.Structures; + +namespace UVtools.WPF +{ + public partial class MainWindow + { + #region Members + public OperationProgress Progress { get; } = new(); + private Timer _progressTimer = new(200) { AutoReset = true }; + private long _progressLastTotalSeconds; + private LogItem _progressLogItem; + private bool _isProgressVisible; + + #endregion + + #region Properties + + public bool IsProgressVisible + { + get => _isProgressVisible; + set => RaiseAndSetIfChanged(ref _isProgressVisible, value); + } + + #endregion + + public void InitProgress() + { + _progressTimer.Elapsed += (sender, args) => + { + var elapsedSeconds = Progress.StopWatch.ElapsedMilliseconds / 1000; + if (_progressLastTotalSeconds == elapsedSeconds) return; + /*Debug.WriteLine(StopWatch.ElapsedMilliseconds); + Debug.WriteLine(elapsedSeconds); + Debug.WriteLine(_lastTotalSeconds);*/ + _progressLastTotalSeconds = elapsedSeconds; + + + Dispatcher.UIThread.InvokeAsync(() => Progress.TriggerRefresh(), DispatcherPriority.Render); + + }; + } + + public void ProgressOnClickCancel() + { + if (!Progress.CanCancel) return; + DialogResult = DialogResults.Cancel; + Progress.CanCancel = false; + Progress.TokenSource.Cancel(); + } + + public void ProgressShow(string title, bool canCancel = true) + { + IsGUIEnabled = false; + Progress.Init(canCancel); + Progress.Title = title; + _progressLogItem = new(title); + + Progress.StopWatch.Restart(); + _progressLastTotalSeconds = 0; + + if (!_progressTimer.Enabled) + { + _progressTimer.Start(); + } + + Progress.TriggerRefresh(); + + IsProgressVisible = true; + + InvalidateVisual(); + } + + public void ProgressFinish() + { + _progressTimer.Stop(); + Progress.StopWatch.Stop(); + _progressLogItem.ElapsedTime = Math.Round(Progress.StopWatch.Elapsed.TotalSeconds, 2); + App.MainWindow.AddLog(_progressLogItem); + IsProgressVisible = false; + InvalidateVisual(); + } + } +} diff --git a/UVtools.WPF/MainWindow.axaml b/UVtools.WPF/MainWindow.axaml index 815468f..7b2f84e 100644 --- a/UVtools.WPF/MainWindow.axaml +++ b/UVtools.WPF/MainWindow.axaml @@ -9,10 +9,10 @@ Icon="/Assets/Icons/UVtools.ico" MinWidth="1024" MinHeight="600" - DragDrop.AllowDrop="True" - > - - <DockPanel> + DragDrop.AllowDrop="True"> + + <Grid RowDefinitions="*" ColumnDefinitions="*"> + <DockPanel Grid.Row="0" Grid.Column="0" IsEnabled="{Binding IsGUIEnabled}"> <Menu DockPanel.Dock="Top"> <MenuItem Name="MainMenu.File" Header="_File"> <MenuItem @@ -568,10 +568,10 @@ Items="{Binding CurrentLayerProperties}"> <DataGrid.Columns> <DataGridTextColumn Header="Name" - Binding="{Binding Content}" + Binding="{Binding Description}" Width="Auto" /> <DataGridTextColumn Header="Value" - Binding="{Binding TagString}" + Binding="{Binding Value}" Width="Auto" /> </DataGrid.Columns> @@ -1751,9 +1751,9 @@ <Grid IsEnabled="{Binding IsFileLoaded}" ColumnDefinitions="*" RowDefinitions="Auto,*,Auto" Margin="5"> - <Grid - IsEnabled="{Binding IsFileLoaded}" - ColumnDefinitions="*,Auto" RowDefinitions="Auto" Margin="5"> + <Grid Grid.Row="0" Grid.Column="0" + IsEnabled="{Binding IsFileLoaded}" + ColumnDefinitions="*,Auto" RowDefinitions="Auto" Margin="5"> <WrapPanel HorizontalAlignment="Left" Grid.Row="0" Orientation="Horizontal"> <ToggleButton IsChecked="{Binding ShowLayerImageRotated}" @@ -2037,4 +2037,50 @@ </DockPanel> + <Grid Grid.Row="0" Grid.Column="0" + RowDefinitions="*,Auto,*" ColumnDefinitions="*,Auto,*" + IsEnabled="{Binding IsProgressVisible}" + IsVisible="{Binding IsProgressVisible}"> + <Border Grid.Row="1" Grid.Column="1" MinWidth="450" + Background="White" + BorderBrush="WhiteSmoke" + BorderThickness="5" + CornerRadius="5"> + <Grid RowDefinitions="Auto,Auto,Auto,Auto" + ColumnDefinitions="*"> + <TextBlock + Grid.Row="0" + Margin="10" Text="{Binding Progress.Title}"/> + <TextBlock + Grid.Row="1" + Margin="10,0,10,10" Text="{Binding Progress.ElapsedTimeStr, StringFormat=Elapsed Time: \{0\}}"/> + <TextBlock + Grid.Row="2" + Margin="10,0,10,10" Text="{Binding Progress.Description}" HorizontalAlignment="Center"/> + + <Grid + Grid.Row="3" + RowDefinitions="30" ColumnDefinitions="*,100"> + <ProgressBar + Grid.Column="0" + Minimum="0" + Maximum="100" + VerticalAlignment="Stretch" + IsIndeterminate="{Binding Progress.IsIndeterminate}" + Value="{Binding Progress.ProgressPercent}" ShowProgressText="True"/> + <Button + IsEnabled="{Binding Progress.CanCancel}" + Command="{Binding ProgressOnClickCancel}" + Grid.Column="1" + IsCancel="True" + VerticalAlignment="Stretch" + HorizontalAlignment="Stretch" + VerticalContentAlignment="Center" + HorizontalContentAlignment="Center" + Content="Cancel"/> + </Grid> + </Grid> + </Border> + </Grid> + </Grid> </uc:WindowEx> diff --git a/UVtools.WPF/MainWindow.axaml.cs b/UVtools.WPF/MainWindow.axaml.cs index 8ed3006..0f2fbe8 100644 --- a/UVtools.WPF/MainWindow.axaml.cs +++ b/UVtools.WPF/MainWindow.axaml.cs @@ -48,7 +48,7 @@ namespace UVtools.WPF #region Controls - public ProgressWindow ProgressWindow = new (); + //public ProgressWindow ProgressWindow = new (); public static MenuItem[] MenuTools { get; } = { @@ -328,7 +328,7 @@ namespace UVtools.WPF #region Members - public Stopwatch LastStopWatch = new Stopwatch(); + public Stopwatch LastStopWatch = new(); private bool _isGUIEnabled = true; private uint _savesCount; @@ -352,14 +352,14 @@ namespace UVtools.WPF if (!RaiseAndSetIfChanged(ref _isGUIEnabled, value)) return; if (!_isGUIEnabled) { - ProgressWindow = new ProgressWindow(); + //ProgressWindow = new ProgressWindow(); return; } - //if (ProgressWindow is null) return; - LastStopWatch = ProgressWindow.StopWatch; - ProgressWindow.Close(); - ProgressWindow.Dispose(); + LastStopWatch = Progress.StopWatch; + ProgressFinish(); + //ProgressWindow.Close(DialogResults.OK); + //ProgressWindow.Dispose(); /*if (Dispatcher.UIThread.CheckAccess()) { ProgressWindow.Close(); @@ -441,6 +441,7 @@ namespace UVtools.WPF InitializeComponent(); App.ThemeSelector?.EnableThemes(this); + InitProgress(); InitInformation(); InitIssues(); InitPixelEditor(); @@ -757,9 +758,7 @@ namespace UVtools.WPF SlicerFile = null; SlicerProperties.Clear(); - Issues.Clear(); - IgnoredIssues.Clear(); - _issuesSliderCanvas.Children.Clear(); + IssuesClear(true); Drawings.Clear(); SelectedTabItem = TabInformation; @@ -862,13 +861,13 @@ namespace UVtools.WPF if (result == ButtonResult.Yes) { IsGUIEnabled = false; + ShowProgressWindow($"Downloading: {VersionChecker.Filename}", false); var task = await Task.Factory.StartNew(async () => { - ShowProgressWindow($"Downloading: {VersionChecker.Filename}"); try { - VersionChecker.AutoUpgrade(ProgressWindow.RestartProgress(false)); + VersionChecker.AutoUpgrade(Progress); return true; } catch (OperationCanceledException) @@ -942,13 +941,13 @@ namespace UVtools.WPF if (SlicerFile is null) return; IsGUIEnabled = false; - + ShowProgressWindow($"Opening: {fileNameOnly}"); + var task = await Task.Factory.StartNew( () => { - ShowProgressWindow($"Opening: {fileNameOnly}"); try { - SlicerFile.Decode(fileName, ProgressWindow.RestartProgress()); + SlicerFile.Decode(fileName, Progress); return true; } catch (OperationCanceledException) @@ -985,27 +984,35 @@ namespace UVtools.WPF return; } - if (SlicerFile is SL1File sl1File && Settings.Automations.AutoConvertSL1Files) + if (Settings.Automations.AutoConvertFiles) { - string fileExtension = sl1File.LookupCustomValue<string>(SL1File.Keyword_FileFormat, null); - if (!string.IsNullOrWhiteSpace(fileExtension)) + string convertFileExtension = SlicerFile switch + { + SL1File sl1File => sl1File.LookupCustomValue<string>(SL1File.Keyword_FileFormat, null), + VDTFile vdtFile => vdtFile.LookupCustomValue<string>(SL1File.Keyword_FileFormat, null), + _ => null + }; + + if (!string.IsNullOrWhiteSpace(convertFileExtension)) { - fileExtension = fileExtension.ToLower(CultureInfo.InvariantCulture); - var convertToFormat = FileFormat.FindByExtension(fileExtension); + convertFileExtension = convertFileExtension.ToLower(CultureInfo.InvariantCulture); + var convertToFormat = FileFormat.FindByExtension(convertFileExtension); if (convertToFormat is not null) { - var directory = Path.GetDirectoryName(sl1File.FileFullPath); - var filename = FileFormat.GetFileNameStripExtensions(sl1File.FileFullPath); + var directory = Path.GetDirectoryName(SlicerFile.FileFullPath); + var filename = FileFormat.GetFileNameStripExtensions(SlicerFile.FileFullPath); FileFormat convertedFile = null; IsGUIEnabled = false; + ShowProgressWindow($"Converting {Path.GetFileName(SlicerFile.FileFullPath)} to {convertFileExtension}"); task = await Task.Factory.StartNew(() => { - ShowProgressWindow($"Converting {Path.GetFileName(SlicerFile.FileFullPath)} to {fileExtension}"); try { - convertedFile = sl1File.Convert(convertToFormat, Path.Combine(directory, $"{filename}.{fileExtension}"), ProgressWindow.RestartProgress()); + convertedFile = SlicerFile.Convert(convertToFormat, + Path.Combine(directory, $"{filename}.{convertFileExtension}"), + Progress); return true; } catch (OperationCanceledException) @@ -1014,7 +1021,8 @@ namespace UVtools.WPF catch (Exception exception) { Dispatcher.UIThread.InvokeAsync(async () => - await this.MessageBoxError(exception.ToString(), "Error while converting the file")); + await this.MessageBoxError(exception.ToString(), + "Error while converting the file")); } return false; @@ -1181,38 +1189,35 @@ namespace UVtools.WPF } } - private async void ShowProgressWindow(string title) + private async void ShowProgressWindow(string title, bool canCancel = true) { if (Dispatcher.UIThread.CheckAccess()) { - ProgressWindow.SetTitle(title); - await ProgressWindow.ShowDialog(this); + ProgressShow(title, canCancel); + + //ProgressWindow.SetTitle(title); + //await ProgressWindow.ShowDialog(this); } else { await Dispatcher.UIThread.InvokeAsync(async () => { - try + ProgressShow(title, canCancel); + /*try { - ProgressWindow.SetTitle(title); - await ProgressWindow.ShowDialog(this); + + //ProgressWindow.SetTitle(title); + //await ProgressWindow.ShowDialog(this); } catch (Exception e) { Debug.WriteLine(e); - } + }*/ }); } } - private void ShowProgressWindowSync(string title) - { - ProgressWindow = new ProgressWindow(title); - } - - - private async void ConvertToOnTapped(object? sender, RoutedEventArgs e) { if (sender is not MenuItem item) return; @@ -1232,13 +1237,13 @@ namespace UVtools.WPF IsGUIEnabled = false; + ShowProgressWindow($"Converting {Path.GetFileName(SlicerFile.FileFullPath)} to {Path.GetExtension(result)}"); var task = await Task.Factory.StartNew(() => { - ShowProgressWindow($"Converting {Path.GetFileName(SlicerFile.FileFullPath)} to {Path.GetExtension(result)}"); try { - return SlicerFile.Convert(fileExtension.GetFileFormat(), result, ProgressWindow.RestartProgress()) is not null; + return SlicerFile.Convert(fileExtension.GetFileFormat(), result, Progress) is not null; } catch (OperationCanceledException) { @@ -1309,14 +1314,13 @@ namespace UVtools.WPF var tempFile = filepath + FileFormat.TemporaryFileAppend; IsGUIEnabled = false; + ShowProgressWindow($"Saving {Path.GetFileName(filepath)}"); var task = await Task.Factory.StartNew( () => { - ShowProgressWindow($"Saving {Path.GetFileName(filepath)}"); - try { - SlicerFile.SaveAs(tempFile, ProgressWindow.RestartProgress()); + SlicerFile.SaveAs(tempFile, Progress); if (File.Exists(filepath)) { File.Delete(filepath); @@ -1383,13 +1387,13 @@ namespace UVtools.WPF string finalPath = Path.Combine(result, fileNameNoExt); IsGUIEnabled = false; + ShowProgressWindow($"Extracting {Path.GetFileName(SlicerFile.FileFullPath)}"); await Task.Factory.StartNew(() => { - ShowProgressWindow($"Extracting {Path.GetFileName(SlicerFile.FileFullPath)}"); try { - SlicerFile.Extract(finalPath, true, true, ProgressWindow.RestartProgress()); + SlicerFile.Extract(finalPath, true, true, Progress); } catch (OperationCanceledException) { @@ -1508,16 +1512,15 @@ namespace UVtools.WPF } IsGUIEnabled = false; + ShowProgressWindow(baseOperation.ProgressTitle, baseOperation.CanCancel); Clipboard.Snapshot(); var result = await Task.Factory.StartNew(() => { - ShowProgressWindow(baseOperation.ProgressTitle); - try { - return baseOperation.Execute(ProgressWindow.RestartProgress(baseOperation.CanCancel)); + return baseOperation.Execute(Progress); } catch (OperationCanceledException) { @@ -1544,11 +1547,16 @@ namespace UVtools.WPF CanSave = true; + if(baseOperation.GetType().Name.StartsWith("OperationCalibrate")) + { + IssuesClear(); + } + switch (baseOperation) { // Tools case OperationRepairLayers operation: - OnClickDetectIssues(); + await OnClickDetectIssues(); break; } } diff --git a/UVtools.WPF/UVtools.WPF.csproj b/UVtools.WPF/UVtools.WPF.csproj index 524c96d..ccdc2f6 100644 --- a/UVtools.WPF/UVtools.WPF.csproj +++ b/UVtools.WPF/UVtools.WPF.csproj @@ -12,7 +12,7 @@ <PackageLicenseFile>LICENSE</PackageLicenseFile> <RepositoryUrl>https://github.com/sn4k3/UVtools</RepositoryUrl> <RepositoryType>Git</RepositoryType> - <Version>2.9.0</Version> + <Version>2.9.1</Version> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> @@ -55,7 +55,6 @@ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> <None Include="..\CHANGELOG.md" Link="CHANGELOG.md" /> - <None Include="..\CreateRelease.WPF.ps1" Link="CreateRelease.WPF.ps1" /> <None Include="..\CREDITS.md" Link="CREDITS.md" /> <None Include="..\LICENSE"> <Pack>True</Pack> diff --git a/UVtools.WPF/UserSettings.cs b/UVtools.WPF/UserSettings.cs index 9d2123f..288e0fe 100644 --- a/UVtools.WPF/UserSettings.cs +++ b/UVtools.WPF/UserSettings.cs @@ -1121,7 +1121,7 @@ namespace UVtools.WPF public sealed class AutomationsUserSettings : BindableBase { private bool _saveFileAfterModifications = true; - private bool _autoConvertSl1Files = true; + private bool _autoConvertFiles = true; private bool _changeOnlyLightOffDelayIfZero = true; private decimal _lightOffDelay = 2.5m; private decimal _bottomLightOffDelay = 3m; @@ -1132,10 +1132,10 @@ namespace UVtools.WPF set => RaiseAndSetIfChanged(ref _saveFileAfterModifications, value); } - public bool AutoConvertSL1Files + public bool AutoConvertFiles { - get => _autoConvertSl1Files; - set => RaiseAndSetIfChanged(ref _autoConvertSl1Files, value); + get => _autoConvertFiles; + set => RaiseAndSetIfChanged(ref _autoConvertFiles, value); } public bool ChangeOnlyLightOffDelayIfZero diff --git a/UVtools.WPF/Windows/SettingsWindow.axaml b/UVtools.WPF/Windows/SettingsWindow.axaml index 1799cf3..16c1ab1 100644 --- a/UVtools.WPF/Windows/SettingsWindow.axaml +++ b/UVtools.WPF/Windows/SettingsWindow.axaml @@ -1431,13 +1431,14 @@ > <StackPanel Orientation="Vertical"> - <TextBlock Padding="10" Background="LightBlue" FontWeight="Bold" Text="PrusaSlicer SL1 files"/> + <TextBlock Padding="10" Background="LightBlue" FontWeight="Bold" Text="File convertion"/> <StackPanel Margin="10" Orientation="Vertical" Spacing="10"> - <CheckBox IsChecked="{Binding Settings.Automations.AutoConvertSL1Files}" - ToolTip.Tip="Converts SL1 files to the format specified on 'PrusaSlicer - Printer - Notes' on 'FILEFORMAT_XXX' variable. + <CheckBox IsChecked="{Binding Settings.Automations.AutoConvertFiles}" + ToolTip.Tip="1) Converts the SL1 files to the format specified on 'PrusaSlicer - Printer - Notes' on 'FILEFORMAT_XXX' variable. +
2) Converts the VDT files to the format specified on 'Voxeldance Tango - Printer settings - Notes' on 'FILEFORMAT_XXX' variable. 
A new file with same name but a new extension will be created and overwrite any previous file. 
After a successful conversion the new file will automatically load in instead of the loaded SL1 file." - Content="Auto convert SL1 files to the target format when possible and load it back"/> + Content="Auto convert SL1 and VDT files to the target format when possible and load it back"/> </StackPanel> </StackPanel> diff --git a/UVtools.WPF/Windows/ToolWindow.axaml b/UVtools.WPF/Windows/ToolWindow.axaml index 6019b5f..ac5caa9 100644 --- a/UVtools.WPF/Windows/ToolWindow.axaml +++ b/UVtools.WPF/Windows/ToolWindow.axaml @@ -11,8 +11,7 @@ Title="Tool" Icon="/Assets/Icons/UVtools.ico"> - - <Grid RowDefinitions="Auto,Auto,Auto,Auto,*,Auto"> + <Grid RowDefinitions="Auto,Auto,Auto,Auto,*,Auto"> <!-- Description --> <Border Grid.Row="0" @@ -420,4 +419,5 @@ </Border> </Grid> + </controls:WindowEx> |