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

时间:2019-04-25 01:00:08

标签: c# wpf user-interface grid

我在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 { }));
        }

是否有更好的方法来执行此操作,或者有时无法显示交叉的问题。

2 个答案:

答案 0 :(得分:2)

WPF GUI更新是在GUI线程中完成的,但是您正在使用Thread.Sleep窗口事件处理程序(该函数本身称为Start函数)中的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();

答案 1 :(得分: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();
                }
            }
        }
    }