在另一个线程中使用XAML中定义的控件

时间:2013-06-21 14:09:51

标签: c# wpf xaml

我正在寻找找到here的库,这有助于在多个线程上呈现UI。该示例工作正常并按预期执行,但我想稍微修改它。

在示例中,将在后面的代码中定义/创建要在单独线程上呈现的可视元素。以下是一些示例代码:

XAML:

<Window x:Class="VisualTargetDemo.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:Microsoft.DwayneNeed.Controls;assembly=Microsoft.DwayneNeed"
    Title="VisualTargetDemo"
    SizeToContent="WidthAndHeight"
    Loaded="OnLoaded"
    >
  <Grid>
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="*"/>
      <ColumnDefinition Width="*"/>
      <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    <local:VisualWrapper Grid.Column="0" Width="200" Height="100" x:Name="Player1"/>
    <local:VisualWrapper Grid.Column="1" Width="200" Height="100" x:Name="Player2"/>
    <local:VisualWrapper Grid.Column="2" Width="200" Height="100" x:Name="Player3"/>
  </Grid>
</Window>

代码隐藏:

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Threading;
using Microsoft.DwayneNeed.Controls;
using Microsoft.DwayneNeed.Threading;

namespace VisualTargetDemo
{
    public partial class Window1 : System.Windows.Window
    {
        public Window1()
        {
            InitializeComponent();
        }

        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            Player1.Child = CreateMediaElementOnWorkerThread();
            Player2.Child = CreateMediaElementOnWorkerThread();
            Player3.Child = CreateMediaElementOnWorkerThread();
        }

        private 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(hostVisual);

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

            return hostVisual;
        }

        private FrameworkElement CreateMediaElement()
        {
            // Create a MediaElement, and give it some video content.
            MediaElement mediaElement = new MediaElement();
            mediaElement.BeginInit();
            mediaElement.Source = new Uri("http://download.microsoft.com/download/2/C/4/2C433161-F56C-4BAB-BBC5-B8C6F240AFCC/SL_0410_448x256_300kb_2passCBR.wmv?amp;clcid=0x409");
            mediaElement.Width = 200;
            mediaElement.Height = 100;
            mediaElement.EndInit();

            return mediaElement;
        }

        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)arg;
            VisualTargetPresentationSource visualTargetPS = new VisualTargetPresentationSource(hostVisual);
            s_event.Set();

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

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



因此,OnLoaded创建并设置VisualWrapper的Child属性。我想要做的是允许用户直接在XAML中定义要分配给Child的控件,如:

<local:VisualWrapper Grid.Column="0" Width="200" Height="100" x:Name="Player1"/>               
  <local:VisualWrapper.ChildSource>
    <Button>TEST</Button>
  </local:VisualWrapper.ChildSource>
</View:VisualWrapper>



计划是将一个ChildSource DependencyProperty添加到VisualWrapper类中,每当该属性更改时,重新创建它的Child内容。问题是当我尝试从VisualWrapper中访问ChildSource控件时:

public FrameworkElement ChildSource
{
    get { return (FrameworkElement)this.GetValue(ChildSourceProperty); }
    set { this.SetValue(ChildSourceProperty, value); }
}

我收到一个错误:“调用线程无法访问此对象,因为另一个线程拥有它。”我无法让它工作,我想知道它是否可能。



我尝试在getter中使用Dispatcher:

public FrameworkElement ChildSource
{
    get { return (FrameworkElement)Dispatcher.Invoke((Delegate)GetValue(ChildSourceProperty)); }
    set { this.SetValue(ChildSourceProperty, value); }
}

但那没用。我的感觉是需要一些Disaptcher.Invoke,但我不确定在哪里。

我想做的是什么?

1 个答案:

答案 0 :(得分:1)

没有

WPF创建具有线程关联的对象,然后只能从创建它们的线程中使用这些对象。这意味着如果要在线程之间拆分UI,则必须在将要使用它的线程上创建每个控件。