不了解wpf更新动态创建的图像控件可见性每秒更改的行为

问题描述 投票:1回答:2

我在wpf中有一个8行8列的网格:

<Window x:Class="Test.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Test"
        mc:Ignorable="d"
        Loaded="Window_Loaded"
        Title="MainWindow" Height="560" Width="800">
    <Grid x:Name="MyGrid" ShowGridLines="True">
        <Grid.ColumnDefinitions>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>
    </Grid>
</Window>

背后的代码是:

public partial class MainWindow : Window
    {
        private const int MaxRow = 8;
        private const int MaxCol = 8;

        public MainWindow()
        {
            InitializeComponent();
            DataContext = this;
        }

        private void Start()
        {
            for (int i = 0; i < MaxRow ; i++)
            {
                for (int j = 0; j < MaxCol ; j++)
                {
                    string current = $"ImgR{i}C{j}";
                    object currentImg = this.FindName(current);

                    if (currentImg?.GetType() == typeof(Image))
                    {

                        var img = ((Image)currentImg);

                        Thread.Sleep(1500);
                        img.Visibility = Visibility.Visible;
                        DoEvents();

                        Thread.Sleep(1500);
                        img.Visibility = Visibility.Hidden;
                        DoEvents();
                    }
                }
            }
        }



        private void Window_Loaded(object sender, RoutedEventArgs e)
        {

            var pngImage = new BitmapImage(new Uri(@"C:\Test\cross.png", UriKind.Absolute));
            for (int i = 0; i < MaxRow ; i++)
            {
                for (int j = 0; j < MaxCol; j++)
                {
                    var img = new Image
                    {
                        Source = pngImage,
                        Name = $"ImgR{i}C{j}",
                        Visibility = Visibility.Hidden
                    };

                    Grid.SetRow(img, i);
                    Grid.SetColumn(img, j);
                    MyGrid.Children.Add(img);
                    RegisterName($"ImgR{i}C{j}", img);
                }
            }
            Start();
        }


        public static void DoEvents()
        {
            Application.Current.Dispatcher.Invoke(DispatcherPriority.Background,
                new Action(delegate { }));
        }
    }   

enter image description here

所以我的想法是动态创建8x8图像并注册它们。然后在两个循环中我改变其可见性。因此产生的效果是图像交叉遍历8x8网格程序似乎正确地做到了,但有时转换不平滑,我的意思是,交叉改变其可见性但是偶尔(程序通常运作良好)没有显示。

我想问题是当我使用以下内容更新Ui时:

public static void DoEvents()
        {
            Application.Current.Dispatcher.Invoke(DispatcherPriority.Background,
                new Action(delegate { }));
        }

有没有更好的方法来做到这一点,或者什么可能是交叉有时没有显示的问题。

c# wpf user-interface grid
2个回答
2
投票

我尝试了代码,对我来说,我甚至看不到十字架,实际上窗口挂起了。这是我所期待的。

您正在UI线程中调用Start方法并运行循环或放置Thread.Sleep。这两个操作都是阻塞的,即它们将使用UI线程中的资源,窗口将挂起。

要解决这个问题,您应该在后台方法/任务中启动该方法。以下应该有效。而不是直接调用Start试试这个:

Task.Run(() => Start());

另外我不了解你的Do Events方法,而且由于现在你的整个Start方法都在后台,你必须确保你负责交叉线程操作。您还需要添加适当的异常处理。

    private void Start()
    {
        for (int i = 0; i < MaxRow; i++)
        {
            for (int j = 0; j < MaxCol; j++)
            {
                string current = $"ImgR{i}C{j}";
                object currentImg = Application.Current.Dispatcher.Invoke(() => this.FindName(current));

                if (currentImg?.GetType() == typeof(Image))
                {

                    var img = ((Image) currentImg);

                    Thread.Sleep(100);
                    Application.Current.Dispatcher.Invoke(() => img.Visibility = Visibility.Visible);

                    //DoEvents();

                    //Thread.Sleep(100);
                    //Application.Current.Dispatcher.Invoke(() => img.Visibility = Visibility.Visible);
                    //DoEvents();
                }
            }
        }
    }

2
投票

WPF GUI更新在GUI线程中完成,但是您在Start函数中使用Thread.Sleep锁定该线程,该函数由窗口Loaded事件处理程序调用,该处理程序本身由GUI线程调用。我可以看到你正在尝试用你的DoEvents函数做什么,但这不是一个更新GUI线程的可靠方法(你已经开始使用GUI线程了,所以你依赖于一些未知的内部行为强制更新的框架)。

并发编程不是一件小事,我建议你在继续编程之前先阅读它。首先,你永远不应该打电话给Thread.Sleep()。线程在C#中已经过时,并且已经被异步编程所取代(在内部可能使用或不使用线程,但这通常不涉及应用程序开发人员)。你需要做的是将你的Start函数修改为异步,例如:

private async Task Start()
{           
    for (int i = 0; i < MaxRow; i++)
    {
        for (int j = 0; j < MaxCol; j++)
        {
            string current = $"ImgR{i}C{j}";
            object currentImg = this.FindName(current);

            if (currentImg?.GetType() == typeof(Image))
            {

                var img = ((Image)currentImg);

                await Task.Delay(TimeSpan.FromMilliseconds(1500));

                Application.Current.Dispatcher.Invoke(() =>
                {
                    img.Visibility = Visibility.Visible;
                });


                await Task.Delay(TimeSpan.FromMilliseconds(1500));

                Application.Current.Dispatcher.Invoke(() =>
                {
                    img.Visibility = Visibility.Hidden;
                });
            }
        }
    }
}

然后在您加载的函数中,您可以使用以下命

private CancellationTokenSource CancelSource;
...
this.CancelSource = new CancellationTokenSource();
Task.Run(Start, this.CancelSource.Token);

如果您需要取消任务,则使用取消令牌源,例如,如果用户关闭窗口:

this.CancelSource.Cancel();
© www.soinside.com 2019 - 2024. All rights reserved.