C# 鼠标悬停在加载屏幕上
我有一个C# 鼠标悬停在加载屏幕上,c#,wpf,multithreading,dispatcher,C#,Wpf,Multithreading,Dispatcher,我有一个加载屏幕,它在一个单独的线程上运行: public partial class LoadingScreen : Window { #region Variables private static Thread _thread; private static LoadingScreen _loading; private static bool _isIndeterminate; #endregion public LoadingScr
加载屏幕
,它在一个单独的线程上运行
:
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:
当我悬停此加载屏幕
包含的两个按钮中的一个时,问题就会发生,我甚至没有单击,只是悬停,不到一秒钟后,Dispatcher.Run()
向我抛出一个调用线程无法访问此对象,因为另一个线程拥有它。
我测试了有或没有自定义样式
和(没有)内容(我删除了向量以简化此问题的代码),没有不同的结果
我可以轻松地拖动以移动窗口
,但不能悬停按钮
。真奇怪。在使用另一个线程并运行调度程序时,我是否错过了什么
编辑:
使用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中),则在显示的每个?
处进行健全的空检查。鼠标悬停发生在GUI线程中,您的按钮逻辑在不同的线程中;解决这个问题,你就会解决问题
将DragMove
操作调用回GUI线程。大概是这样的:
public static void SafeOperationToGuiThread(Action operation)
{
System.Windows.Application.Current?.Dispatcher?.Invoke(operation);
}
称为
SafeOperationToGuiThread( DragMove );
如果您没有使用C#6(在VS 2015中),则在显示的每个?
处进行健全的空检查。,因此这是由于工具提示。这就解释了为什么你必须把鼠标放在上面一小段时间。我会把它加到我的测试中,看看我能想出什么
编辑:我相信这个问题是由于你的方式开始你的线程。您正在从主窗口调用启动屏幕吗?如果是,则需要更改代码的顺序:
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 dispatcher线程调用的,按照您设置线程的方式,它总是取消主窗口。当我像这样设置它时,我会得到与您相同的问题。通过更改线程创建,它将按预期工作
有关详细信息,请参阅ShowSplash和OpenSplash方法中的更改
原创帖子:我试图复制你的代码,但我无法让它失败。请看下面我的代码,它在您的系统上工作吗
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>
所以这是由于工具提示。这就解释了为什么你必须把鼠标放在上面一小段时间。我会把它加到我的测试中,看看我能想出什么
编辑:我相信这个问题是由于你的方式开始你的线程。您正在从主窗口调用启动屏幕吗?如果是,则需要更改代码的顺序:
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 dispatcher线程调用的,按照您设置线程的方式,它总是取消主窗口。当我像这样设置它时,我会得到与您相同的问题。通过更改线程创建,它将按预期工作
有关详细信息,请参阅ShowSplash和OpenSplash方法中的更改
原创帖子:我试图复制你的代码,但我无法让它失败。请看下面我的代码,它在您的系统上工作吗
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>
鼠标悬停发生在GUI线程中,按钮逻辑在另一个线程中;解决这个问题,您就会解决问题。什么是Dispatcher.Run()
?只需使用ShowDialog()
而不是Show()
,线程将不会继续运行,直到窗口关闭。如果您试图创建线程安全的启动屏幕,那么您会失败。当您注释掉ApartmentState.STA
行时会发生什么?同样的行为会发生吗?@O