Dispatch Timer是静态的,但需要非静态行为

时间:2013-09-21 19:06:12

标签: c# wpf

我有一个应用程序,其核心是键盘钩子。这个钩子允许我打开和关闭我为制造公司工作的平板电脑上的一些功能。该程序最初是作为WinForms应用程序开始的,首先制作它的开发人员不再与该公司合作。长话短说,程序中有一些需要修复的错误,所以我花了一些时间来清理代码库,并更新UI以获得更清新的感觉。其中一个WinForms窗口显示在屏幕的底部中心500ms,其中包含您刚修改的选项的图片。例如,如果您刚刚禁用蓝牙,则屏幕底部会显示灰色的蓝牙符号。

我最近一直在努力停止使用Winforms,我想要更新UI以使用WPF的挑战。我想出了这个代码来替换之前显示的屏幕窗口(实际的xaml没有显示,因为这不是问题)

public partial class OnScreenDisplayDialog : Window
{
    public static void ShowUserUpdate(Enums.OnScreenDisplayOptions target)
    {
        var f = new OnScreenDisplayDialog();
        var imageSource = GetPictureFromOptions(target);
        f.targetImage.Source = new BitmapImage(new Uri(imageSource, UriKind.Relative));
        f.Show();
        f.Top = SystemParameters.PrimaryScreenHeight - f.ActualHeight; 
        f.StartCloseTimer();
    }
    private OnScreenDisplayDialog()
    {
        InitializeComponent();
    }

    private void StartCloseTimer()
    {
        DispatcherTimer timer = new DispatcherTimer();
        timer.Interval = TimeSpan.FromMilleseconds(500);
        timer.Tick += TimerTick;
        timer.Start();
    }

    private void TimerTick(object sender, EventArgs e)
    {
        DispatcherTimer timer = (DispatcherTimer)sender;
        timer.Stop();
        timer.Tick -= TimerTick;
        var sb = new Storyboard();
        var fadeout = new DoubleAnimation(0, new Duration(TimeSpan.FromSeconds(1)));
        Storyboard.SetTargetProperty(fadeout, new System.Windows.PropertyPath("Opacity"));
        sb.Children.Add(fadeout);
        sb.Completed += closeStoryBoard_Completed;
        this.BeginStoryboard(sb);
    }

    private void closeStoryBoard_Completed(object sender, EventArgs e)
    {
        this.Close();
    }
}

所以我的想法是我只想像这样对这个窗口进行一次调用

    Forms.OnScreenDisplayDialog.ShowUserUpdate(Enums.OnScreenDisplayOptions.BluetoothOn);

我似乎遇到的问题是,通过像下面代码这样的简单测试,我得到了一些意想不到的结果

            Forms.OnScreenDisplayDialog.ShowUserUpdate(Enums.OnScreenDisplayOptions.BluetoothOn);
            System.Threading.Thread.Sleep(1000);
            Forms.OnScreenDisplayDialog.ShowUserUpdate(Enums.OnScreenDisplayOptions.BrightnessDown);
            System.Threading.Thread.Sleep(1000);
            Forms.OnScreenDisplayDialog.ShowUserUpdate(Enums.OnScreenDisplayOptions.BrightnessUp);

所以我希望第一个窗口显示500毫秒然后消失。 500ms后,下一个窗口显示(重复)。但相反,所有3个窗口仍然可见,即使在大约10秒后,窗口仍然可见。如果我点击其他地方(使他们失去焦点),他们就会关闭。而我无法为我的生活找出原因。我在Storyboard完成方法上设置了一个断点,并且所有3个同时完成(这让我觉得计时器是静态的,而不是动态的)。

有人可以帮我弄清楚我在做错了吗?

修改

因此,正如我的评论所提到的,我想展示我在代码中测试的内容。这里有2件事。一个是从我的程序的主要入口点进行快速简便的测试。第二个是在我的键盘钩子设置并运行后运行的。通常我会过滤掉被按下的键并采取相应的行动,但在这种情况下,如果按下或释放按键,我打开此窗口。所以这是代码

的主要入口点部分
    [STAThread]
    static void Main()
    {
        InstantiateProgram();

        EnsureApplicationResources();
        if (true)
        {
            new System.Threading.Thread(Test).Start();
            System.Threading.Thread.Sleep(1000);
            new System.Threading.Thread(Test).Start();
            System.Threading.Thread.Sleep(1000);
            new System.Threading.Thread(Test).Start();
        }
        singleton = new System.Threading.Mutex(true, "Keymon");
        if (!singleton.WaitOne(System.TimeSpan.Zero, true)) return;

        AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
        System.Windows.Forms.Application.Run(main);
    }
    private static void Test()
    {
        Console.Beep(1000, 200);
        System.Windows.Application.Current.Dispatcher.Invoke(new Action(delegate
        {
            Forms.OnScreenDisplayDialog.ShowUserUpdate((Enums.OnScreenDisplayOptions)r.Next(0,7));
        }));
        //System.Threading.Thread.Sleep(1000);
    }
    static Random r = new Random();
    public static void EnsureApplicationResources()
    {
        if (System.Windows.Application.Current == null)
        {
            // create the Application object
            new System.Windows.Application();
            System.Windows.Application.Current.ShutdownMode = ShutdownMode.OnExplicitShutdown;
            // merge in your application resources
            System.Windows.Application.Current.Resources.MergedDictionaries.Add(
                System.Windows.Application.LoadComponent(
                    new Uri("/Themes/Generic.xaml",
                    UriKind.Relative)) as ResourceDictionary);
        }
    }
    private static void InstantiateProgram()
    {
        System.Windows.Forms.Application.EnableVisualStyles();
        System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false);
        main = new frmMain();
    }

这是我提到的在初始化主表单后使用的代码

    public frmMain()
    {
        InitializeComponent();
        theHook = new KeyboardHookLibrary.KeyboardHook();
        theHook.KeyDown += theHook_KeyDown;
        theHook.KeyUp += theHook_KeyUp;
        theHook.Start();
    }
    ~frmMain()
    {
        theHook.Dispose();
    }
    void theHook_KeyUp(int key)
    {
        Forms.OnScreenDisplayDialog.ShowUserUpdate(Enums.OnScreenDisplayOptions.BrightnessDown);

    }

    void theHook_KeyDown(int key)
    {
        Forms.OnScreenDisplayDialog.ShowUserUpdate(Enums.OnScreenDisplayOptions.BluetoothOn);
    }

以防万一我将包括屏幕显示的xaml

<Window x:Class="XPKbdMon.Forms.OnScreenDisplayDialog"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             Height="200"
            Width="200"
        WindowStyle="None"
        AllowsTransparency="True"
        Background="Black"

        ShowInTaskbar="False">
    <!--WindowStartupLocation="CenterScreen"-->
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Image x:Name="targetImage" />
        <ProgressBar x:Name="brightnessBar" Grid.Row="1" Visibility="Collapsed" Height="30"/>
    </Grid>
</Window>

1 个答案:

答案 0 :(得分:1)

我认为问题在于测试代码而不是实际的动画代码。你在主(GUI)线程上调用Thread.Sleep(1000),对吧?这将有效地阻止动画和DispatcherTimer。

尝试在其他线程上运行测试代码。但是,您需要确保使用Dispatcher.Invoke或BeginInvoke在GUI线程上运行对ShowUserUpdate的调用。