C# WPF如何在GUI空闲时显示Gif动画?

C# WPF如何在GUI空闲时显示Gif动画?,c#,wpf,animation,gif,C#,Wpf,Animation,Gif,代码示例: XAML 当我第一次点击combobox时,我会在5-7秒内看到GUI冻结,我想在GUI冻结时显示动画gif。我该怎么做呢?您的问题是要创建一个包含14000个项目的组合框。这将需要很长时间来创建,并且在创建之后将无法使用。你真的希望你的用户从14000个列表中选择一个项目吗 重新考虑你的用户界面。如果你真的需要用户从14000种可能性列表中选择,考虑一个带有Type前行的搜索文本字段。 如果您必须拥有一个包含数千项的组合框,请查看。您需要这样的组合框: 第一个VisualWrapp

代码示例:

XAML


当我第一次点击combobox时,我会在5-7秒内看到GUI冻结,我想在GUI冻结时显示动画gif。我该怎么做呢?

您的问题是要创建一个包含14000个项目的组合框。这将需要很长时间来创建,并且在创建之后将无法使用。你真的希望你的用户从14000个列表中选择一个项目吗

重新考虑你的用户界面。如果你真的需要用户从14000种可能性列表中选择,考虑一个带有Type前行的搜索文本字段。
如果您必须拥有一个包含数千项的组合框,请查看。

您需要这样的组合框:

第一个
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;
    }
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

    }
然后
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>
代码:

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);
}

1-在WPF中,有比动画GIF更好的方式显示动画。2-有更好的方法防止UI冻结,例如在后台线程中执行繁重的任务。我的繁重任务是GUI绘图。我想向用户展示我的应用程序在制作时可以正常工作。我可以问一下什么样的GUI绘图吗?不管怎样,Willem的解决方案似乎很好,如果你在UI线程中做大量工作,这基本上是你唯一能做的。它正在绘制包含近3000个元素的Telerik ComboBox。我会试试Willem的解决方案。我还想提一下,没有“画”组合框这样的事情。如果您正在使用WPF,则必须放弃winforms思维模式。数据/行为和表示之间必须有一个清晰的分离,你们永远不会有这样的问题。我只为样本创建了14000个comboItems。我用这个简单的例子来说明我的GUI冻结问题。在我的应用程序中,当我的用户控件为draw youself时,GUI冻结时间为5-10秒,我想在此期间显示“加载”动画。对不起,您能告诉我使用ImageBehavior和VisualTargetPresentationSource需要添加哪些引用吗?是一个外部dll。我错过了另一节课。看看我的最新资料。现在它可以编译了。但是我在Cb.Items.Add(“Item number”+I)行上看到“调用线程无法访问此对象,因为另一个线程拥有它”;还有一些修正:如果我不能正确理解,那么您可以在其他过程中开始填充comboitems,但在我的变体中,它不会引发GUI freez。当WPF在第一个组合框下拉列表中绘制组合项时,会出现GUI freez。您需要查看的是和
ScrollViewer.IsDeferredScrollingEnabled=“True”
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

    }
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);
}
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>