【问题标题】:WPF How to show Gif animation when GUI is freezy?WPF GUI冻结时如何显示Gif动画?
【发布时间】:2013-02-07 05:50:54
【问题描述】:

代码示例:

XAML

<Window x:Class="TestWpfApp.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:testWpfApp="clr-namespace:TestWpfApp"
    Title="MainWindow" Height="350" Width="525">
<Grid>

    <testWpfApp:GifImage x:Name="gifImage" Stretch="None" GifSource="/loading.gif" AutoStart="True" Width="50" Height="50" Margin="0,0,300,0"/>
    <ComboBox Width="200" Height="40" Name="Cb"></ComboBox>
</Grid>
</Window>

其中GifImage 是来自How do I get an animated gif to work in WPF? 的高评价答案的类

C# 代码隐藏

public partial class MainWindow : Window
{


    public MainWindow()
    {
        InitializeComponent();
        for (int i = 0; i < 14000; i++)
        {
            Cb.Items.Add("Item number" + i);
        }


    }
}

当我第一次点击组合框时,我会在 5-7 秒内冻结 GUI,我想在 GUI 冻结时显示动画 gif。我怎样才能做到?

【问题讨论】:

  • 1 - 在 WPF 中,有比动画 GIF 更好的方式来显示动画。 2 - 有更好的方法来防止 UI 冻结,例如在后台线程中执行繁重的任务。
  • 我的繁重任务 - 是 GUI 绘图。我想向用户展示我的应用程序在制作时正在运行。
  • 我能问一下是什么样的 GUI 绘图吗?无论如何,Willem 的解决方案看起来不错,如果您在 UI 线程中做繁重的工作,基本上只能这样做。跨度>
  • 正在绘制具有近 3000 个元素的 Telerik ComboBox。将尝试 Willem 解决方案。
  • 我还想提一下,没有“绘制”组合框之类的东西。如果您正在使用 WPF,则必须将 winforms 思维模式抛在脑后。数据/行为和表示之间必须存在明确的分离,您永远不会遇到这类问题。

标签: c# wpf animation gif


【解决方案1】:

你想要这样的东西:

第一个VisualWrapper类:

[ContentProperty("Child")]
    public class VisualWrapper : FrameworkElement
    {
        public Visual Child
        {
            get
            {
                return _child;
            }

            set
            {
                if (_child != null)
                {
                    RemoveVisualChild(_child);
                }

                _child = value;

                if (_child != null)
                {
                    AddVisualChild(_child);
                }
            }
        }

        protected override Visual GetVisualChild(int index)
        {
            if (_child != null && index == 0)
            {
                return _child;
            }
            else
            {
                throw new ArgumentOutOfRangeException("index");
            }
        }

        protected override int VisualChildrenCount
        {
            get
            {
                return _child != null ? 1 : 0;
            }
        }

        private Visual _child;
    }

然后VisualTargetPresentationSource:

public class VisualTargetPresentationSource : PresentationSource
    {
        public VisualTargetPresentationSource(HostVisual hostVisual)
        {
            _visualTarget = new VisualTarget(hostVisual);
        }

        public override Visual RootVisual
        {
            get
            {
                return _visualTarget.RootVisual;
            }

            set
            {
                Visual oldRoot = _visualTarget.RootVisual;

                // Set the root visual of the VisualTarget.  This visual will
                // now be used to visually compose the scene.
                _visualTarget.RootVisual = value;

                // Tell the PresentationSource that the root visual has
                // changed.  This kicks off a bunch of stuff like the
                // Loaded event.
                RootChanged(oldRoot, value);

                // Kickoff layout...
                UIElement rootElement = value as UIElement;
                if (rootElement != null)
                {
                    rootElement.Measure(new Size(Double.PositiveInfinity,
                                                 Double.PositiveInfinity));
                    rootElement.Arrange(new Rect(rootElement.DesiredSize));
                }
            }
        }

        protected override CompositionTarget GetCompositionTargetCore()
        {
            return _visualTarget;
        }

        public override bool IsDisposed
        {
            get
            {
                // We don't support disposing this object.
                return false;
            }
        }

        private VisualTarget _visualTarget;
    }

这在你的 XAML 中:

<Window x:Class="TestWpfApp.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:testWpfApp="clr-namespace:TestWpfApp"
    Title="MainWindow" Height="350" Width="525"
    xmlns:vw="--NamespaceHere--">
<Grid>

    <testWpfApp:GifImage x:Name="gifImage" Stretch="None" GifSource="/loading.gif" AutoStart="True" Width="50" Height="50" Margin="0,0,300,0"/>
    <ComboBox Width="200" Height="40" Name="Cb"></ComboBox>

<vw:VisualWrapper Height="160" Width="160" VerticalAlignment="Center" HorizontalAlignment="Center" x:Name="visualWrapper" />
</Grid>
</Window>

然后LoadingPanel类:

public class LoadingPanel
    {
        private VisualWrapper.VisualWrapper visualWrapper;

        public LoadingPanel(VisualWrapper.VisualWrapper _visualWrapper)
        {
            visualWrapper = _visualWrapper;
        }

        public VisualWrapper.VisualWrapper VisualWrapper
        {
            get { return visualWrapper; }
            set { visualWrapper = value; }
        }

        #region WaitDailog

        public HostVisual CreateMediaElementOnWorkerThread()
        {
            // Create the HostVisual that will "contain" the VisualTarget
            // on the worker thread.
            HostVisual hostVisual = new HostVisual();

            // Spin up a worker thread, and pass it the HostVisual that it
            // should be part of.

            Thread thread = new Thread(new ParameterizedThreadStart(MediaWorkerThread));
            thread.SetApartmentState(ApartmentState.STA);
            thread.IsBackground = true;
            thread.Start(new object[] { hostVisual, visualWrapper });

            // Wait for the worker thread to spin up and create the VisualTarget.
            s_event.WaitOne();

            return hostVisual;
        }

        private FrameworkElement CreateMediaElement(VisualWrapper visualWrapper)
        {
            BitmapImage bi = new BitmapImage(new Uri(--YOURIMAGEPATH--));//Image path goes here
            Image image = new Image();
            image.Source = bi;
            image.Height = 150;
            image.Width = 150;
            //image.Margin = new Thickness(-150, -150, -150, -150);

            ImageBehavior.SetAnimatedSource(image, bi);//See http://wpfanimatedgif.codeplex.com/

            BrushConverter conv = new BrushConverter();
            //SolidColorBrush brush = conv.ConvertFromString("#6C8BBA") as SolidColorBrush;
            Border border = new Border();
            border.Background = Brushes.Transparent;
            //border.BorderBrush = brush;
            //border.BorderThickness = new Thickness(3);
            //border.Margin = new Thickness(-85, -140, 0, 0);

            border.Child = image;

            return border;
        }

        private void MediaWorkerThread(object arg)
        {
            // Create the VisualTargetPresentationSource and then signal the
            // calling thread, so that it can continue without waiting for us.
            HostVisual hostVisual = (HostVisual)((object[])arg)[0];
            VisualWrapper visualWrapper = (VisualWrapper)((object[])arg)[1];

            VisualTargetPresentationSource visualTargetPS = new VisualTargetPresentationSource(hostVisual);
            s_event.Set();

            // Create a MediaElement and use it as the root visual for the
            // VisualTarget.
            visualTargetPS.RootVisual = CreateMediaElement(visualWrapper);

            // Run a dispatcher for this worker thread.  This is the central
            // processing loop for WPF.
            System.Windows.Threading.Dispatcher.Run();
        }

        private static AutoResetEvent s_event = new AutoResetEvent(false);

        public bool ShowWaitDialog()
        {
            if (visualWrapper != null)
            {
                if (visualWrapper.Child == null)
                {
                    visualWrapper.Child = CreateMediaElementOnWorkerThread();
                }
            }

            return true;
        }

        public bool DisposeWaitDialog()
        {
            if (visualWrapper != null)
            {
                visualWrapper.Child = null;
            }

            return true;
        }

        #endregion

    }

in 代码:

public MainWindow()
{
    InitializeComponent();

    VisualWrapper visualWrapper = (VisualWrapper)this.FindName("visualWrapper");

    LoadingPanel loadingPanel = new LoadingPanel(visualWrapper);

    Dispatcher.Invoke((Action)(() =>
    {
        loadingPanel.ShowWaitDialog();
    }), DispatcherPriority.Send, null);

     Task.Factory.StartNew(() =>
    {
        List<string> list = new List<string>();

        for (int i = 0; i < 14000; i++)
        {
            list.Add("Item number" + i);
        }

        Dispatcher.BeginInvoke((Action)(() =>
        {
            Cb.ItemsSource = list;
        }), DispatcherPriority.Normal, null);
    }, TaskCreationOptions.LongRunning);
}

编辑VirtualizingStackPanel:

An easy way to implement this is to create an ItemsPanelTemplate as a Resource and reference it in the ComboBox markup.  

  <Window.Resources>
    <ItemsPanelTemplate x:Key="VSP">
      <VirtualizingStackPanel/>
    </ItemsPanelTemplate>
  </Window.Resources>


    <ComboBox Height="23" Margin="27,10,10,0" Name="ComboBox1"
             VerticalAlignment="Top"
             ItemsSource="{Binding}"
             ItemsPanel="{StaticResource VSP}"
             ScrollViewer.IsDeferredScrollingEnabled="True">
    </ComboBox>  
Specifically, the ItemsPanel property of the ComboBox is set to that ItemsPanelTemplate Resource.

If you prefer, you can include the VirtualizingStackPanel right in the ComboBox creation markup: 

   <ComboBox Height="23" Margin="27,10,10,0" Name="ComboBox1"
             VerticalAlignment="Top"
             ItemsSource="{Binding}"
             ScrollViewer.IsDeferredScrollingEnabled="True">
      <ComboBox.ItemsPanel>
        <ItemsPanelTemplate>
          <VirtualizingStackPanel />
        </ItemsPanelTemplate>
      </ComboBox.ItemsPanel>
    </ComboBox> 

【讨论】:

  • 抱歉,您能告诉我使用 ImageBehavior 和 VisualTargetPresentationSource 需要添加哪些参考吗?
  • ImageBehavior 是一个外部 dll。我错过了另一节课。查看我的更新
  • 谢谢。现在它编译。但是我在行 Cb.Items.Add("Item number" + i); 上得到“调用线程无法访问这个对象,因为另一个线程拥有它”还有一些修复:如果我理解正确,而不是在其他过程中开始填充组合项,但在我的变体中,它不会引发 GUI 冻结。当 WPF 在第一个组合框下拉菜单上绘制组合项时,会发生 GUI 冻结
  • 你需要查看的是VirtualizingStackPanelScrollViewer.IsDeferredScrollingEnabled="True"
【解决方案2】:

您的问题是您正在创建一个包含 14000 个项目的 ComboBox。这将需要很长时间才能创建,并且在创建后将无法使用。您真的希望您的用户从 14000 个列表中选择一个项目吗?

重新考虑您的用户界面。如果您确实需要让用户从 14000 种可能性的列表中进行选择,请考虑使用预先输入的搜索文本字段。

如果您绝对必须拥有一个包含数千个项目的 ComboBox,请查看 control virtualization

【讨论】:

  • 我只为示例创建了 14000 个组合项。我做了这个简单的例子来展示我的 GUI 冻结问题。在我的应用程序中,当我的 UserControl 自己绘制时,我的 GUI 冻结时间为 5-10 秒,我想在那段时间显示“加载”动画。
猜你喜欢
  • 2012-06-07
  • 2013-06-24
  • 1970-01-01
  • 2011-08-07
  • 1970-01-01
  • 2022-06-12
  • 2010-12-20
相关资源
最近更新 更多