在矩阵循环期间显示进度?

Display progress during matrix loop?

我正在制作一个棋盘,我想在完成时查看进度。 Chessboard 不是 classical,它包含数百万个字段,创建过程本身需要时间。有时创建过程最多需要 2 分钟。我想直观地看到流程本身何时结束。它不一定是进度条,它可以是任何不会减慢进程本身的控件。

当我使用 Progress.Dispatcher.Invoke (()... 时,我实际上减慢了创建过程,它比平时花费了 5 倍的时间。当我使用 BackgroundWorkerReportProgress 时......我也减慢了创建过程,比平时多花 5 到 8 倍。

我只想使用任何不会减慢进程的控件或 class 向用户显示进度。有什么想法吗?

Rectangle[,] square = new Rectangle[x, x];
    for (int row = 0; row < x; row++)
        for (int col = 0; col < x; col++)
        {
            square[row, col] = new Rectangle()
            {
                Height = squareSize,
                Width = squareSize
            };
            Grid.SetColumn(square[row, col], col);
            Grid.SetRow(square[row, col], row);
            if ((row + col) % 2 == 0)
            {
                square[row, col].Fill = new SolidColorBrush(System.Windows.Media.Color.FromRgb(233, 223, 191));
            }
            else
            {
                square[row, col].Fill = new SolidColorBrush(System.Windows.Media.Color.FromRgb(112, 42, 44));
            }
            LayoutRoot.Children.Add(square[row, col]);

            // Watch process of creation in real time
            if (cbCreationProcess.IsChecked == true)
                Progress.Dispatcher.Invoke(() => Progress.Value = x, DispatcherPriority.Background);
        }

这个解决方案过去对我有用。

ProgressWindowControl.xaml

<Window
    x:Class="YourNamespace.ProgressWindowControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    WindowStartupLocation="CenterScreen" ShowInTaskbar="False" ResizeMode="NoResize"
    SizeToContent="WidthAndHeight" WindowStyle="None"
    mc:Ignorable="d">

    <Window.Style>
        <Style TargetType="Window">
            <Setter Property="AllowsTransparency" Value="True"/>
            <Setter Property="Background" Value="#00FFFFFF"/>
        </Style>
    </Window.Style>

    <Grid>
        <Grid Width="450" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0">
            <Grid x:Name="Back">
                <Border Background="Black" CornerRadius="3" Opacity="0.15"/>
                <Border CornerRadius="2" Margin="1" Background="White"/>
            </Grid>
            <Grid x:Name="Content_Area" Margin="12">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="*" />
                    <RowDefinition Height="Auto" />
                </Grid.RowDefinitions>
                <TextBlock x:Name="Info" TextWrapping="Wrap" 
                           Text="{Binding Path=State,RelativeSource={RelativeSource AncestorType={x:Type Window}}}" 
                           Grid.Row="0" Margin="12,12,12,0" Foreground="#FF2D2D2D"/>
                <ProgressBar Height="12"
                             Grid.Row="1"
                             Margin="12"
                             IsIndeterminate="{Binding Path=IsIndeterminate,RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
                             Value="{Binding Path=Progress,RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
                             Maximum="{Binding Path=MaxProgress,RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />

                <Button x:Name="uxCancelBtn" Grid.Row="2" Height="22" Width="85" HorizontalAlignment="Right" Margin="0 0 12 0"
                        Click="CancelButton_Click" IsEnabled="False" Content="{x:Static resx:Strings.Cancel}">               
                </Button>
            </Grid>
        </Grid>
    </Grid>
</Window>

ProgressWindowControl.cs

public sealed partial class ProgressWindowControl : Window
{
    public static readonly DependencyProperty ProgressProperty =
     DependencyProperty.Register("Progress", typeof(double), typeof(ProgressWindowControl), new PropertyMetadata(0d));

    public static readonly DependencyProperty MaxProgressProperty =
     DependencyProperty.Register("MaxProgress", typeof(double), typeof(ProgressWindowControl), new PropertyMetadata(100d));

    public static readonly DependencyProperty IsIndeterminateProperty =
        DependencyProperty.Register("IsIndeterminate", typeof(bool), typeof(ProgressWindowControl), new PropertyMetadata(true));

    public static readonly DependencyProperty StateProperty =
    DependencyProperty.Register("State", typeof(string), typeof(ProgressWindowControl), new PropertyMetadata(string.Empty));

    public static readonly DependencyProperty IsCancelAllowedProperty =
     DependencyProperty.Register("IsCancelAllowed", typeof(bool), typeof(ProgressWindowControl), new PropertyMetadata(false));

    private ProgressWindowControl()
    {
        InitializeComponent();
    }

    public double Progress
    {
        get
        {
            return (double)GetValue(ProgressProperty);
        }
        set
        {
            SetValue(ProgressProperty, value);
        }
    }

    public double MaxProgress
    {
        get
        {
            return (double)GetValue(MaxProgressProperty);
        }
        set
        {
            SetValue(MaxProgressProperty, value);
        }
    }

    public bool IsIndeterminate
    {
        get
        {
            return (bool)GetValue(IsIndeterminateProperty);
        }
        set
        {
            SetValue(IsIndeterminateProperty, value);
        }
    }

    public string State
    {
        get
        {
            return (string)GetValue(StateProperty);
        }
        set
        {
            SetValue(StateProperty, value);
        }
    }

    public Action OnProgressWindowCancel { get; set; }

    private void CancelButton_Click(object sender, RoutedEventArgs e)
    {
        if (OnProgressWindowCancel != null)
        {
            uxCancelBtn.IsEnabled = false;
            uxCancelBtn.Content = Strings.Cancelling;
            OnProgressWindowCancel();
        }
    }

    [DllImport("user32.dll")]
    private static extern int SetWindowLong(IntPtr hwnd, int index, int newStyle);

    private const int GWL_HWNDPARENT = -8;

    private static ProgressWindowControl _progressWindowControl;
    private static bool _isVisible;
    private static Window _owner;
    private static ResizeMode? _ownerResizeMode;
    private static bool _ownerIsHitTestVisible;
    private static bool _ownerFocusable;

    public static void ShowProgressWindow(Window owner = null)
    {
        if (!_isVisible)
        {
            IntPtr ownerHandle = IntPtr.Zero;
            if (owner != null)
            {
                _owner = owner;
                ownerHandle = GetHandler(_owner);
                //Block owner window input while the progress bar is opened
                _ownerResizeMode = _owner.ResizeMode;
                _ownerIsHitTestVisible = _owner.IsHitTestVisible;
                _ownerFocusable = _owner.Focusable;
                _owner.ResizeMode = ResizeMode.NoResize;
                _owner.IsHitTestVisible = false;
                _owner.Focusable = false;
                _owner.PreviewKeyDown += Owner_PreviewKeyDown;
                _owner.PreviewMouseDown += Owner_PreviewMouseDown;
                _owner.Closing += Owner_Closing;
            }
            //Run window in its own thread
            Thread thread = new Thread(new ThreadStart(() =>
            {
                SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext(Dispatcher.CurrentDispatcher));
                _progressWindowControl = new ProgressWindowControl();
                // Shutdown the dispatcher when the window closes
                _progressWindowControl.Closed += (s, e) =>
                    Dispatcher.CurrentDispatcher.BeginInvokeShutdown(DispatcherPriority.Background);

                // When the progress window has loaded, if an owner has been specified, attach it to the window, otherwise set Topmost = true
                ProgressWindowControl._progressWindowControl.Loaded += (s, e) =>
                {
                    if (owner != null)
                    {
                        IntPtr ownedWindowHandle = GetHandler(_progressWindowControl);
                        SetOwnerWindowMultithread(ownedWindowHandle, ownerHandle);
                    }
                    else
                    {
                        _progressWindowControl.Topmost = true;
                    }
                };
                _progressWindowControl.Show();
                _isVisible = true;
                System.Windows.Threading.Dispatcher.Run();
            }));
            thread.SetApartmentState(ApartmentState.STA);
            thread.IsBackground = true;
            thread.Start();
        }
    }

    private static void Owner_Closing(object sender, System.ComponentModel.CancelEventArgs e)
    {
        e.Cancel = true;
    }

    private static void Owner_PreviewMouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
    {
        e.Handled = true;
    }

    private static void Owner_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
    {
        e.Handled = true;
    }

    private static void SetOwnerWindowMultithread(IntPtr windowHandleOwned, IntPtr intPtrOwner)
    {
        if (windowHandleOwned != IntPtr.Zero && intPtrOwner != IntPtr.Zero)
        {
            SetWindowLong(windowHandleOwned, GWL_HWNDPARENT, intPtrOwner.ToInt32());
        }
    }

    private static IntPtr GetHandler(Window window)
    {
        var interop = new WindowInteropHelper(window);
        return interop.Handle;
    }

    public static void CloseProgressWindow()
    {
        if (_progressWindowControl != null && _isVisible)
        {
            if (_progressWindowControl.Dispatcher.CheckAccess())
            {
                _progressWindowControl.Close();
            }
            else
            {
                _progressWindowControl.Dispatcher.Invoke(DispatcherPriority.Normal,
                    new ThreadStart(_progressWindowControl.Close));
            }
            if (_owner != null)
            {
                //Unblock owner input
                _owner.ResizeMode = _ownerResizeMode ?? ResizeMode.CanResize;
                _owner.IsHitTestVisible = _ownerIsHitTestVisible;
                _owner.Focusable = _ownerFocusable;
                _owner.PreviewKeyDown -= Owner_PreviewKeyDown;
                _owner.PreviewMouseDown -= Owner_PreviewMouseDown;
                _owner.Closing -= Owner_Closing;
            }
            //Reset fields
            _ownerResizeMode = null;
            _ownerIsHitTestVisible = false;
            _ownerFocusable = false;
            _progressWindowControl = null;
            _owner = null;
            _isVisible = false;
        }
    }

    public static void SetProgress(double progress, double maxProgress)
    {
        if (_progressWindowControl != null)
        {
            if (_progressWindowControl.Dispatcher.CheckAccess())
            {
                _progressWindowControl.IsIndeterminate = false;
                _progressWindowControl.Progress = progress;
                _progressWindowControl.MaxProgress = maxProgress;
            }
            else
            {
                _progressWindowControl.Dispatcher.Invoke(DispatcherPriority.Normal,
                    new ThreadStart(() =>
                    {
                        _progressWindowControl.IsIndeterminate = false;
                        _progressWindowControl.Progress = progress;
                        _progressWindowControl.MaxProgress = maxProgress;
                    }));
            }
        }
    }

    public static void SetIsIndeterminate(bool isIndeterminate)
    {
        if (_progressWindowControl != null)
        {
            if (_progressWindowControl.Dispatcher.CheckAccess())
            {
                _progressWindowControl.IsIndeterminate = isIndeterminate;
            }
            else
            {
                _progressWindowControl.Dispatcher.Invoke(DispatcherPriority.Normal,
                    new ThreadStart(() =>
                    {
                        _progressWindowControl.IsIndeterminate = isIndeterminate;
                    }));
            }
        }
    }

    public static void SetState(string state)
    {
        if (_progressWindowControl != null)
        {
            if (_progressWindowControl.Dispatcher.CheckAccess())
            {
                _progressWindowControl.State = state;
            }
            else
            {
                _progressWindowControl.Dispatcher.Invoke(DispatcherPriority.Normal,
                    new ThreadStart(() =>
                    {
                        _progressWindowControl.State = state;
                    }));
            }
        }
    }

    public static void SetIsCancelAllowed(bool isCancelAllowed, Action progressWindowCancel)
    {
        if (_progressWindowControl != null)
        {
            if (_progressWindowControl.Dispatcher.CheckAccess())
            {
                _progressWindowControl.OnProgressWindowCancel = progressWindowCancel;
                _progressWindowControl.uxCancelBtn.IsEnabled = isCancelAllowed;
                _progressWindowControl.uxCancelBtn.Content = Strings.Cancel;
            }
            else
            {
                _progressWindowControl.Dispatcher.Invoke(DispatcherPriority.Normal,
                    new ThreadStart(() =>
                    {
                        _progressWindowControl.OnProgressWindowCancel = progressWindowCancel;
                        _progressWindowControl.uxCancelBtn.IsEnabled = isCancelAllowed;
                        _progressWindowControl.uxCancelBtn.Content = Strings.Cancel;
                    }));
            }
        }
    }
}

帮手class打开window:

public static class ProgressWindowHelper
{
    public static void Show(Window owner = null)
    {
        ProgressWindowControl.ShowProgressWindow(owner);
    }

    public static void Close()
    {
        ProgressWindowControl.CloseProgressWindow();
    }

    public static void SetProgress(double progress, double maxProgress)
    {
        ProgressWindowControl.SetProgress(progress, maxProgress);
    }

    public static void SetIsIndeterminate(bool isIndeterminate)
    {
        ProgressWindowControl.SetIsIndeterminate(isIndeterminate);
    }

    public static void SetState(string state)
    {
        ProgressWindowControl.SetState(state);
    }

    public static void SetIsCancelAllowed(bool isCancelAllowed, Action progressWindowCancel)
    {
        ProgressWindowControl.SetIsCancelAllowed(isCancelAllowed, progressWindowCancel);
    }
}

一个服务,这样你就可以使用依赖注入(我没有包含接口,只是根据需要创建一个):

 public class ProgressWindowService : IProgressWindowService
{
    public void Show(Window owner = null)
    {
        ProgressWindowHelper.Show(owner);
    }

    public void Close()
    {
        ProgressWindowHelper.Close();
    }

    public void SetProgress(double progress, double maxProgress)
    {
        ProgressWindowHelper.SetProgress(progress, maxProgress);
    }

    public void SetIsIndeterminate(bool isIndeterminate)
    {
        ProgressWindowHelper.SetIsIndeterminate(isIndeterminate);
    }

    public void SetState(string state)
    {
        ProgressWindowHelper.SetState(state);
    }

    public void SetIsCancelAllowed(bool isCancelAllowed, Action progressWindowCancel)
    {
        ProgressWindowHelper.SetIsCancelAllowed(isCancelAllowed, progressWindowCancel);
    }
}

最后在您的 ViewModel 中(假设您已注入服务):

ProgressBarService.SetProgress(current, total);

ProgressBarService.SetState(state);

ProgressBarService.Show();

您也可以将 window 传递给 Show 方法,然后 window 将附加到它并在显示进度 window 时阻止输入,如果没有window 提供,进度条显示在任何其他 window (TopMost=true):

之上
ProgressBarService.Show(YourWindow);

您也可以使用信使来触发进度window。

编辑

删除了 DevExpress 依赖项。

很明显,添加任何功能都会减慢它的速度,正如您所知。

我会做什么 如果我被告知使用上面的代码很可能会创建一个计时器并插入你当前的 rowcol 递增,这取决于你如何处理你的计算。
您所要做的就是处理 Timer.Elapsed 事件(无论哪种计时器)和 calculate/report 当前进度百分比等。
否则,您可以编写一个单独的线程,然后 运行 它处于一个循环中,该循环休眠 N 毫秒直到绘图完成——本质上与使用计时器的想法串联。通常,这是我们在报告播放 mp3 或在单独线程上操作的某些此类对象的进度时会做的事情。

这里有很大的空间供您发挥创造力,并从这种情况中汲取一些智慧来优化结果,但在考虑此类问题之前...
请记住,WPF 在处理绘图方面是一个野兽,因为它在很大程度上依赖于视频硬件、内存等......我倾向于将 WPF 视为 OpenGL 或 DirectX 的 html .我希望你的 GPU 与热同步连接良好,并且不会变得太热 and/or,以至于你不是在带有嵌入式 GPU 的笔记本电脑上工作。我在这里很激进,但这只是因为多年来我在 write/compile/run/and-repeat 周期中编写软件时已经耗尽了硬件。如果我玩得更安全一点,我会从很多硬件中获得更多的生命。更不用说最近几年我们的硬件越来越难了。

为了尽可能有创意

  • 因为我们知道有多少个正方形并且可以确定生成的网格的尺寸,您是否考虑过将自定义图案渲染为背景并明确设置网格的大小网格?完成后,您可以在进一步填充所需内容或启动一些有趣的过渡等方面发挥创意
  • learning/using System.Threading.Thread(ThreadStart) 将使您能够将进程优先级设置为比标准默认值更高的值——而不是使用 BackgroundWorker 或其他类似的...但我没有我完全沉浸在这样的实现中。
  • 在 UI 之外构建一些对象,例如 List<> 并在行、列或每个 N incremenet
  • 我们知道最后有 x*x 个盒子,所以如果我们明确尺寸,这里就有很大的创意空间。我已经有一段时间没有修改 WPF 了,但这将是一个很好的案例,可以用来研究和寻找不同的方式来布置这样的东西(例如:CustomLayoutPanel tut)。如果你坚持网格的概念,你能做的就只有这么多了——我会本能地使用绘图方法来做这件事,并且可能会想出一种方法来只绘制在网格上可见的东西根据需要屏幕...或
  • 一般来说,@dymanoid 的回答是把你引向正确的方向,但它打破了所问问题的逻辑,因此超出了问题的范围……但我们想在这里启发一下,通常是任何渲染过程的东西在 WPF 中应该考虑使用现有的优化策略,例如 MVVM 和 styles/templates/transitions.