鼠标崩溃加载屏幕

时间:2016-06-29 12:43:46

标签: c# wpf multithreading dispatcher

我有LoadingScreen分隔Thread

public partial class LoadingScreen : Window
{
    #region Variables

    private static Thread _thread;
    private static LoadingScreen _loading;
    private static bool _isIndeterminate;

    #endregion

    public LoadingScreen()
    {
        InitializeComponent();
    }

    #region Methods

    public static void ShowSplash(bool isIndeterminate = false)
    {
        _isIndeterminate = isIndeterminate;

        _thread = new Thread(OpenSplash)
        {
            IsBackground = true,
            Name = "Loading"
        };
        _thread.SetApartmentState(ApartmentState.STA);
        _thread.Start();
    }

    private static void OpenSplash()
    {
        _loading = new LoadingScreen();
        _loading.ProgressBar.IsIndeterminate = _isIndeterminate;
        _loading.ShowDialog();

        _loading.Dispatcher.Invoke(new Action(Dispatcher.Run));
    }

    public static void CloseSplash()
    {
        if (_loading == null || _thread == null)
            return;

        _loading.Dispatcher.Invoke(new Action(() => { _loading.Close(); }));
        _loading.Dispatcher.InvokeShutdown();
        _loading = null;
    }

    public static void Update(int value, string description)
    {
        Update((double)value, description);
    }

    public static void Update(double value, string description)
    {
        if (_loading == null)
            return;

        _loading.Dispatcher.Invoke((Action)delegate
        {
            var da = new DoubleAnimation(value, new Duration(TimeSpan.FromMilliseconds(Math.Abs(_loading.ProgressBar.Value - value) * 12)))
            {
                EasingFunction = new PowerEase { Power = 3 }
            };

            _loading.ProgressBar.BeginAnimation(RangeBase.ValueProperty, da);
            _loading.TextBlock.Text = description;
        });
    }

    public static void Status(bool isIndeterminate)
    {
        if (_loading == null)
            return;

        _loading.Dispatcher.Invoke((Action)delegate
       {
           _loading.ProgressBar.IsIndeterminate = isIndeterminate;
       });
    }

    #endregion

    private void SplashScreen_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        DragMove();
    }

    private void RestoreButton_OnClick(object sender, RoutedEventArgs e)
    {
        _loading.Dispatcher.Invoke((Action)delegate
        {
            if (_loading.MiddleRowDefinition.Height == new GridLength(1, GridUnitType.Star))
            {
                _loading.MiddleRowDefinition.Height = new GridLength(0);
                _loading.RestoreButton.Content = "R";
                _loading.Height = 69;
            }
            else
            {
                _loading.MiddleRowDefinition.Height = new GridLength(1, GridUnitType.Star);
                _loading.RestoreButton.Content = "A"; //I removed my vector to test.
                _loading.Height = 200;
            }
        });
    }

    private void MinimizeButton_OnClick(object sender, RoutedEventArgs e)
    {
        _loading.Dispatcher.Invoke((Action)delegate
        {
            _loading.WindowState = WindowState.Minimized;
        });
    }
}

的Xaml:

<Window x:Class="MySoftware.Util.LoadingScreen"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Bem Vindo" Height="200" Width="400" Topmost="True"
    WindowStartupLocation="CenterScreen" WindowStyle="None" ResizeMode="NoResize"
    MouseLeftButtonDown="SplashScreen_OnMouseLeftButtonDown">

<Window.Resources>
    <Storyboard x:Key="ShowStoryBoard">
        <DoubleAnimation Storyboard.TargetProperty="Opacity" From="0.0" To="1.0" Duration="0:0:5">
            <DoubleAnimation.EasingFunction>
                <BackEase Amplitude="2" EasingMode="EaseIn"/>
            </DoubleAnimation.EasingFunction>
        </DoubleAnimation>
    </Storyboard>

    <Storyboard x:Key="HideStoryBoard">
        <DoubleAnimation Storyboard.TargetProperty="Opacity" From="1" To="0" Duration="0:0:5">
            <DoubleAnimation.EasingFunction>
                <PowerEase Power="4" EasingMode="EaseIn"/>
            </DoubleAnimation.EasingFunction>
        </DoubleAnimation>
    </Storyboard>
</Window.Resources>

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="30"/>
        <RowDefinition x:Name="MiddleRowDefinition"/>
        <RowDefinition Height="21"/>
        <RowDefinition Height="18"/>
    </Grid.RowDefinitions>

    <Grid.Background>
        <RadialGradientBrush Center="0.5,1.3" GradientOrigin="0.5,1.1">
            <RadialGradientBrush.RelativeTransform>
                <TransformGroup>
                    <ScaleTransform CenterY="0.5" CenterX="0.5" ScaleY="0.75" ScaleX="0.8"/>
                    <SkewTransform CenterY="0.5" CenterX="0.5"/>
                    <RotateTransform CenterY="0.5" CenterX="0.5"/>
                    <TranslateTransform x:Name="TranslateTransform" X="-0.5"/>
                </TransformGroup>
            </RadialGradientBrush.RelativeTransform>

            <GradientStop Color="#FFE2E1EE" Offset="0.1"/>
            <GradientStop Color="#FF70A6C8" Offset="1"/>
        </RadialGradientBrush>
    </Grid.Background>

    <StackPanel Grid.Row="0" Orientation="Horizontal" HorizontalAlignment="Right" Height="30">
        <StackPanel.Resources>
            <Style TargetType="{x:Type Button}" x:Key="WindowButtonStyle">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="{x:Type ButtonBase}">
                            <Border x:Name="Chrome" BorderBrush="{TemplateBinding BorderBrush}"
                                    Margin="0" Background="{TemplateBinding Background}"
                                    SnapsToDevicePixels="True">
                                <Viewbox MaxHeight="15" MaxWidth="15" Stretch="Uniform">
                                    <ContentPresenter ContentTemplate="{TemplateBinding ContentTemplate}"
                                                      Content="{TemplateBinding Content}"
                                                      ContentStringFormat="{TemplateBinding ContentStringFormat}"
                                                      HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                                      Margin="{TemplateBinding Padding}"
                                                      RecognizesAccessKey="True"
                                                      SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
                                                      VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
                                </Viewbox>
                            </Border>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>

                <!--Default Values-->
                <Setter Property="Background" Value="Transparent"/>
                <Setter Property="FontSize" Value="16" />
                <Setter Property="Foreground" Value="Black" />
                <Setter Property="Margin" Value="0,0,5,0"/>

                <Style.Triggers>
                    <Trigger Property="IsMouseOver" Value="True">
                        <Setter Property="Background" Value="#60FFFFFF" />
                    </Trigger>
                    <Trigger Property="IsPressed" Value="True">
                        <Setter Property="Background" Value="#90FFFFFF" />
                    </Trigger>
                    <Trigger Property="IsEnabled" Value="False">
                        <Setter Property="Opacity" Value=".6"/>
                        <Setter Property="Effect" Value="{x:Null}"/>
                    </Trigger>
                </Style.Triggers>
            </Style>
        </StackPanel.Resources>

        <Button x:Name="MinimizeButton" Style="{StaticResource WindowButtonStyle}"
                Content="--" Width="30" Margin="0" Height="30"
                Click="MinimizeButton_OnClick"
                ToolTip="Minimize" ToolTipService.Placement="Top"/>
        <Button x:Name="RestoreButton" Style="{StaticResource WindowButtonStyle}"
                Content="R" Width="30" Margin="0" Height="30"
                Click="RestoreButton_OnClick"
                ToolTip="Restore" ToolTipService.Placement="Top"/>
    </StackPanel>

    <Image Grid.Row="1" Stretch="Uniform" StretchDirection="DownOnly"
           Margin="20"/>

    <TextBlock Grid.Row="2" x:Name="TextBlock" Text="Loading..." HorizontalAlignment="Center" Foreground="#FF202020"
               FontSize="16"/>

    <ProgressBar Grid.Row="3" x:Name="ProgressBar" Margin="4" Height="10" Maximum="100"/>
</Grid>

当我将这个LoadingScreen包含的两个按钮中的一个按下时,问题就发生了,我甚至没有点击,只是悬停,不到一秒钟之后,Dispatcher.Run()会给我一个The calling thread cannot access this object because a different thread owns it. {1}}

我测试了是否有自定义Style和(out)内容(我删除了我的向量以简化此问题的代码),没有不同的结果。

我可以轻松拖动以移动Window,但我无法悬停Buttons。那很奇怪。在使用其他线程并运行Dispatcher时错过了什么?

编辑:

使用ShowDialog()代替Show()没有做任何事 删除Dispatcher.Run()杀了我的应用程序的其他部分(没有其他任何东西出现,只是一个白色的屏幕) 删除STA会导致应用程序崩溃(需要是STA)。

编辑2:

整个代码都可用。 该应用程序使用.net 4.0,我忘了说。

2 个答案:

答案 0 :(得分:2)

所以这是由于工具提示。这就解释了为什么你必须在短时间内将鼠标悬停在它上面。我会把它添加到我的测试中,看看我能想出什么。

编辑:我认为问题是由于你开始你的线程的方式。你是从主窗口调用启动画面吗?如果是,您需要更改代码的顺序:

  public static void ShowSplash(bool isIndeterminate = false)
    {
        _thread = new Thread(new ThreadStart(OpenSplash))
        {
            IsBackground = true,
            Name = "Loading"
        };

        _thread.SetApartmentState(ApartmentState.STA);
        _thread.Start();
    }

    private static void OpenSplash()
    {
        //Run on another thread.
        _splash = new Splash();
        _isIndeterminate = false;

       _splash.Show();
        System.Windows.Threading.Dispatcher.Run();

        //_splash.Dispatcher.Invoke(new Action(Dispatcher.Run));
        System.Windows.Threading.Dispatcher.Run(); //HERE!
        _splash.Dispatcher.Invoke(() => { _splash.ProgressBar.IsIndeterminate = _isIndeterminate; });

    }

在我的原始帖子中,我正在从App.xaml.cs文件创建窗口,此时没有调度程序正在运行,这就是它工作的原因。当从窗口调用它时,它是从主Windows调度程序线程调用的,用你设置线程的方式总是调用主窗口。当我这样设置时,我会得到与你相同的问题。通过更改线程创建,它将按预期工作。

有关详细信息,请参阅ShowSplash和OpenSplash方法中的更改

ORINGINAL POST :我尝试复制代码,但我无法让它失败。请参阅下面的代码,它是否适用于您的系统?

App.cs

public partial class App : Application
{
    private static Splash _splash;
    private static bool _isIndeterminate = false;
    private static Thread _thread;

    protected override void OnStartup(StartupEventArgs e)
    {
        ShowSplash(true);

        Thread.Sleep(5000);
    }

    public static void ShowSplash(bool isIndeterminate = false)
    {
        _isIndeterminate = isIndeterminate;

        _thread = new Thread(OpenSplash)
        {
            IsBackground = true,
            Name = "Loading"
        };
        _thread.SetApartmentState(ApartmentState.STA);
        _thread.Start();
    }

    private static void OpenSplash()
    {
        //Run on another thread.
        _splash = new Splash();
        _splash.Show();

        _splash.ProgressBar.IsIndeterminate = _isIndeterminate;
        //_splash.Dispatcher.Invoke(new Action(Dispatcher.Run));
        System.Windows.Threading.Dispatcher.Run(); //HERE!
    }

    public static void CloseSplash()
    {
        if (_splash == null || _thread == null)
            return;

        _splash.Dispatcher.Invoke(new Action(() => { _splash.Close(); }));
        _splash.Dispatcher.InvokeShutdown();
        _splash = null;
    }

    public static void Update(int value, string description)
    {
        Update((double)value, description);
    }

    public static void Update(double value, string description)
    {
        if (_splash == null)
            return;

        _splash.Dispatcher.Invoke((Action)delegate
        {
            //It takes 120 ms to progress 10%.
            var da = new DoubleAnimation(value, new Duration(TimeSpan.FromMilliseconds(Math.Abs(_splash.ProgressBar.Value - value) * 12)))
            {
                EasingFunction = new PowerEase { Power = 3 }
            };

            _splash.ProgressBar.BeginAnimation(RangeBase.ValueProperty, da);
            _splash.textBlock.Text = description;
        });
    }


}

Splash.xaml.cs

public partial class Splash : Window
{
    public Splash()
    {
        InitializeComponent();
    }

    private void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        this.DragMove();
    }
}

Splash xaml

<Window x:Class="SplashScreenWPF.Splash"
    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:SplashScreenWPF"
    mc:Ignorable="d"
    Title="Splash" Height="300" Width="300" MouseLeftButtonDown="Window_MouseLeftButtonDown">
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="147*"/>
        <ColumnDefinition Width="145*"/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="135*"/>
        <RowDefinition Height="134*"/>
    </Grid.RowDefinitions>
    <Button  Margin="30" Grid.Column="0" />
    <Button  Margin="30" Grid.Column="2" />
    <TextBlock x:Name="textBlock" HorizontalAlignment="Left" Margin="30,66,0,0" Grid.Row="1" TextWrapping="Wrap" Text="TextBlock" VerticalAlignment="Top"/>
    <ProgressBar x:Name="ProgressBar" Grid.Column="1" HorizontalAlignment="Left" Height="16" Margin="24,66,0,0" Grid.Row="1" VerticalAlignment="Top" Width="100"/>

</Grid>

答案 1 :(得分:0)

悬停发生在GUI线程中,您拥有的按钮逻辑位于不同的线程中;解决这个问题,你将解决问题。

DragMove操作调用回GUI线程。像这样:

public static void SafeOperationToGuiThread(Action operation)
{
   System.Windows.Application.Current?.Dispatcher?.Invoke(operation);
}

称为

 SafeOperationToGuiThread( DragMove );

如果你没有使用C#6(在VS 2015中),那么在显示的每个?上进行健全性无效检查。