鼠标悬停在加载屏幕上时崩溃

Mouse Over Crashes Loading Screen

我有一个 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.

我在使用或不使用自定义 Style 以及使用(不使用)内容(我删除了矢量以简化此问题的代码)的情况下进行了测试,没有不同的结果。

我可以轻松地拖动来移动 Window,但我不能将鼠标悬停在 Buttons 上。真奇怪。我在处理另一个线程时是否遗漏了一些东西 运行 a Dispatcher?

编辑:

使用 ShowDialog() 而不是 Show() 没有任何作用。
删除 Dispatcher.Run() 杀死了我应用程序的另一部分(没有其他任何东西出现,只是一个白屏)。
删除 STA 会使应用程序崩溃(它需要是 STA)。

编辑 2:

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

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

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

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

被称为

 SafeOperationToGuiThread( DragMove );

如果您不使用 C# 6(在 VS 2015 中),则在显示的每个 ? 处进行完整性空值检查。

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

编辑:我认为问题出在您启动线程的方式上。您是从 Main Window 调用启动画面吗?如果是,则需要更改代码的顺序:

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

    }

在我原来的 post 中,我从 App.xaml.cs 文件创建 window,这里当时没有调度程序 运行,这就是它起作用的原因。当它从 window 调用时,它是从主 Windows 调度程序线程调用的,按照您设置线程的方式,它总是从主 window 调用。当我这样设置时,我会遇到与您相同的问题。通过更改线程创建,它将按您的预期工作。

有关详细信息,请参阅 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();
    }
}

飞溅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>