1

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

好吧,我最近一直想停止一起使用 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 毫秒然后消失。500 毫秒后下一个窗口显示(重复)。但相反,所有 3 个窗口仍然可见,即使大约 10 秒后,这些窗口仍然可见。如果我单击其他地方(使它们失去焦点),它们就会关闭。而且我无法为我的生活找出原因。我在 Storyboard completed 方法上设置了一个断点,并且所有 3 个同时完成(这让我认为计时器是静态的,而不是动态的)。

有人可以帮我弄清楚我在这里做错了什么吗?

编辑

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

    [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>
4

1 回答 1

1

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

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

于 2013-09-21T19:48:05.460 回答