我为服务台团队创建了Wallboard应用程序,该应用程序将WPF用于前端,并将Cisco的电话数据库用于后端。该应用程序由两个显示不同信息的屏幕组成,这些屏幕显示在同一屏幕中,并用System.Timers.Timer
彼此切换。
创建该应用程序是为了如果WindowA
可见,则显示WindowB
,然后隐藏WindowA
。当一个窗口可见时,该窗口的计时器再次变为活动状态,以恢复数据库调用,而另一个窗口的计时器被禁用:
private static void InterfaceChanger_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
if (WindowA.Visibility == Visibility.Visible)
{
WindowAEnabled = false;
ChangeVisibility(Visibility.Visible, WindowB);
WindowBEnabled = true;
WindowB_Elapsed(null, null); // force the call of the timer's callback
ChangeVisibility(Visibility.Collapsed, WindowA);
}
else
{
WindowBEnabled = false;
ChangeVisibility(Visibility.Visible, WindowA);
WindowAEnabled = true;
WindowA_Elapsed(null, null); // force the call of the timer's callback
ChangeVisibility(Visibility.Collapsed, WindowB);
}
}
private static void ChangeVisibility(Visibility visibility, Window window)
{
window.Dispatcher.Invoke(DispatcherPriority.Normal, (SendOrPostCallback)delegate
{
window.Visibility = visibility;
}, null);
}
问题是,这种方法在最多90%的时间内都可以正常工作。问题是,有时,例如,如果
WindowA's
可见性更改为“可见”,而WindowB's
可见性更改为“折叠”,则WindowB
会折叠,但WindowA
则需要2-3秒才能显示,而大多数情况下WindowA
变为可见,而WindowB
折叠时则看不到。这(当它不起作用时)导致桌面而不是应用程序可见。我最初使用
DispatcherPriority.Background
,但是这导致屏幕转换器在70%到80%的时间内都能正常工作,因此我决定针对DispatcherPriority.Normal
进行更改(DispatcherPriority.Send
的结果与正常情况基本相同)。问题:
考虑到这是在四核CPU中以x64模式运行的,这是Dispatcher预期的正常行为吗?
知道查询是在未等待的异步方法中执行的,因此Dispatcher不应优先于这些方法吗?
有另一种方法(不使用Dispatcher或使用另一个Window属性)来完成我正在寻找的东西吗?
这是用于访问/启动Windows的代码:
//WindowA:
<Application x:Class="MyNamespace.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="WindowA.xaml">
//WindowA class:
public static WindowA WindowAInstance;
public WindowA()
{
// unnecessary code hidden
WindowAInstance = this;
WindowB b = new WindowB;
}
// WindowB class
public static WindowB WindowBInstance;
public WindowB()
{
// unnecessary code hidden
WindowBInstance = this;
}
// this is the code that starts the timers
public static void StartTimersHandling()
{
Database.RemoveAgents();
InterfaceChangerTimer = new System.Timers.Timer();
InterfaceChangerTimer.Interval = ApplicationArguments.InterfaceChangerTime;
InterfaceChangerTimer.Elapsed += InterfaceChanger_Elapsed;
InterfaceChangerTimer.AutoReset = true;
InterfaceChangerTimer.Start();
WindowATimer = new System.Timers.Timer();
WindowATimer.Interval = 1000;
WindowATimer.Elapsed += WindowATimer_Elapsed;
WindowATimer.AutoReset = true;
WindowATimer.Start();
WindowBTimer = new System.Timers.Timer();
WindowBTimer.Interval = 1000;
WindowBTimer.Elapsed += WindowBTimer_Elapsed;
WindowBTimer.AutoReset = true;
WindowBTimer.Start();
}
最佳答案
听起来您正在编写信息亭应用程序(即全屏,非交互式)。如果是这种情况,我认为您最好拥有一个窗口并在其中切换视图,而不要在两个单独的窗口之间切换。另外,您需要将数据库查询工作与刷新窗口内容分开。此外,我认为如果视图之间彼此不了解也将有所帮助:目前,您的第一个窗口与第二个窗口紧密相连,这并不是一个好主意。
我认为,如果您对体系结构进行了一些更改,那么您遇到的许多问题将消失。这是我的建议:
首先,只需打开一个窗口。创建两个用户控件(“项目”>“添加用户控件”),然后将XAML布局从现有窗口移至这两个新控件中。然后使您的主窗口看起来像这样:
<Window x:Class="StackOverflow.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:StackOverflow"
WindowState="Maximized" WindowStyle="None">
<Grid>
<my:UserControl1 x:Name="_first" Panel.ZIndex="1" />
<my:UserControl2 Panel.ZIndex="0" />
</Grid>
<Window.Triggers>
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard>
<Storyboard AutoReverse="True" RepeatBehavior="Forever">
<ObjectAnimationUsingKeyFrames BeginTime="0:0:5" Duration="0:0:5"
Storyboard.TargetName="_first"
Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0:0:0"
Value="{x:Static Visibility.Hidden}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Window.Triggers>
</Window>
这是一个没有镶边的全屏窗口,其中包含您的两个用户控件(本质上是现有窗口的内容)。它们被放置在
Grid
元素中,以便一个位于另一个顶部:我正在使用Panel.ZIndex
属性将第一个控件强制置于桩的顶部。最后,我正在使用一个动画(在加载窗口时触发),该动画在一定时间后切换其中一个控件的可见性以将其隐藏。动画设置为重复和自动反转,其效果是隐藏其中一个控件,然后使其再次可见。您可以更改Duration
属性值以控制每个控件“保持”可见的时间。在此示例中将其设置为5秒,这意味着开关之间有10秒的延迟。这项工作的关键是,第一个用户控件在可见时必须完全遮盖它下面的其他用户控件。通过设置控件的背景色很容易实现。
您的用户控件可以包含窗口将包含的任何内容。这是我使用的示例用户控件XAML:
<UserControl x:Class="StackOverflow.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Background="White" Padding="40">
<TextBlock Text="{Binding Number}" FontSize="60"
TextAlignment="Center" VerticalAlignment="Top" />
</UserControl>
如您所见,它只是一个
TextBlock
元素,其Text
属性绑定到用户控件的代码背后定义的Number
属性。我为两个用户控件使用了相同的XAML,只是更改了文本的VerticalAlignment
,以便我可以分辨出在任何给定时间可见的控件。后面的代码看起来像这样(除了类名,两者都一样):
using System;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Threading;
namespace StackOverflow
{
public partial class UserControl1 : UserControl, INotifyPropertyChanged
{
public UserControl1()
{
InitializeComponent();
DataContext = this;
_timer = new DispatcherTimer
{ Interval = TimeSpan.FromSeconds(5), IsEnabled = true };
_timer.Tick += (sender, e) => Task.Run(async () => await DoWorkAsync());
}
readonly DispatcherTimer _timer;
readonly Random _random = new Random();
public event PropertyChangedEventHandler PropertyChanged;
public int Number
{
get
{
return _number;
}
private set
{
if (_number != value)
{
_number = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Number"));
}
}
}
}
int _number;
async Task DoWorkAsync()
{
// Asynchronous code started on a thread pool thread
Console.WriteLine(GetType().Name + " starting work");
_timer.IsEnabled = false;
try
{
await Task.Delay(TimeSpan.FromSeconds(_random.Next(4, 12)));
Number++;
}
finally
{
_timer.IsEnabled = true;
}
Console.WriteLine(GetType().Name + " finished work");
}
}
}
它基本上包含一个单独的
Number
属性(实现INotifyPropertyChanged
),该属性通过“工作者”方法递增。 worker方法由计时器调用:在这里,我使用的是DispatcherTimer
,但是由于我没有直接更改任何UI元素,因此任何.NET计时器都可以完成。安排该工作程序使用
Task.Run
在线程池上运行,然后异步运行。我正在通过Task.Delay
等待一段时间来模拟长时间运行的作业。此工作程序方法将是从中调用数据库查询的地方。您可以通过设置计时器的Interval
属性来改变连续查询之间的间隔。没什么可说的,查询之间的间隔必须与UI的刷新间隔(即,两个视图的切换速度)相同。确实,由于您的查询所花费的时间是可变的,因此无论如何同步两者都是很棘手的。