0

我的 C# 程序遇到了一些问题。我想创建秒表(从某个值开始倒计时),即在按下某个键时启动。为了处理键,我使用低级键盘钩子。但是这个类有静态方法,所以如果我想从不同的类调用一个方法,那不是静态的,我必须创建一个新实例。随着倒计时,我想每滴答(秒)更改元素的Text属性。问题是,当我必须在静态方法中创建一个新的类实例时TextBox,如何更改TextBox每个刻度(在类中)的属性,因此将不再响应以前的. 我的代码工作得很好,按键被识别,计时器正在倒计时并分别显示秒值CountdownCountdownTextBoxTextBoxMessageBox'es(用于调试),但不会更改表单中的文本。

如果它可以帮助您理解我上面写的内容,我可以给您我的代码。就在评论里这么说吧。

提前感谢您的帮助。

编码:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;


namespace stopwatch2
{
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        InterceptKeys.InterceptInit();
    }

    private void Form1_Closing(object sender, CancelEventArgs e)
    {
        InterceptKeys.Unhook();
    }

    public void changeText(string text)
    {

        MessageBox.Show(text); //for debug
        textBox1.Text = text;
    }


    class InterceptKeys
    {

        private const int WH_KEYBOARD_LL = 13;
        private const int WM_KEYDOWN = 0x0100;
        private static LowLevelKeyboardProc _proc = HookCallback;
        private static IntPtr _hookID = IntPtr.Zero;

        public static void InterceptInit()
        {
            _hookID = SetHook(_proc);
        }

        public static void Unhook()
        {
            UnhookWindowsHookEx(_hookID);
        }

        private static IntPtr SetHook(LowLevelKeyboardProc proc)
        {
            using (Process curProcess = Process.GetCurrentProcess())
            using (ProcessModule curModule = curProcess.MainModule)
            {
                return SetWindowsHookEx(WH_KEYBOARD_LL, proc,
                    GetModuleHandle(curModule.ModuleName), 0);
            }
        }

        private delegate IntPtr LowLevelKeyboardProc(
            int nCode, IntPtr wParam, IntPtr lParam);

        public static IntPtr HookCallback(
            int nCode, IntPtr wParam, IntPtr lParam)
        {


            if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
            {
                int vkCode = Marshal.ReadInt32(lParam);

                Countdown timer = new Countdown(); //creating new instance

                if ((Keys)vkCode == Keys.Home)
                {

                    timer.StartTimer();

                }

                if ((Keys)vkCode == Keys.End)
                {

                    timer.StopTimer();

                }

            }
            return CallNextHookEx(_hookID, nCode, wParam, lParam);
        }

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr SetWindowsHookEx(int idHook,
            LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool UnhookWindowsHookEx(IntPtr hhk);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
            IntPtr wParam, IntPtr lParam);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr GetModuleHandle(string lpModuleName);


    }

   public partial class Countdown : Form1
    {

        public System.Windows.Forms.Timer timer1;
        public int counter = 60;

        public void StartTimer()
        {

            timer1 = new System.Windows.Forms.Timer();
            timer1.Tick += new EventHandler(timer1_Tick);
            timer1.Interval = 1000; // 1 second
            timer1.Start();
            changeText(counter.ToString());

        }

        public void timer1_Tick(object sender, EventArgs e)
        {

            counter--;
            if (counter == 0)
                counter = 60;

            changeText(counter.ToString());

        }

        public void StopTimer()
        {
            timer1.Stop();
        }

     }

  }

}
4

1 回答 1

2

所以,首先,你不想Countdown扩展Form1. 这给您一种错误印象,即您可以访问 的成员Form1,但您不能。每次使用它自己的文本框创建一个新实例时,您都在创建一个全新的表单Countdown,并且它是自己的……一切。

更糟糕的是,Countdown每次触发钩子处理程序时都会创建一个新事件,因此您不会在Countdown之前启动的同一实例上停止计时器。

InterceptKeys也不应该是 的内部类Form1,它应该是它自己文件中的独立类。

老实说,我什至认为这个Countdown类不应该存在。它的方法应该属于另外两个类之一。

让我们先来看一下InterceptKeys。您可以看到 90% 的课程只是在创建挂钩并确定它是否是您“关心”的事件。然后,当我们关心的事情发生时,我们只有一点点代码(那 10%)来做任何需要发生的事情。

有一种方法可以更有效地解决“关注点分离”问题。该类InterceptKeys只需要处理设置键盘钩子并过滤掉我们不关心的那些的样板代码。我们想把最后 10% 的代码移出这个类。事件是一个很好的方式来做到这一点。从逻辑上讲,这里发生了两个“事件”,一个是按下 home 键时,另一个是按下 end 键时。因此,我们将从在内部创建这两个事件开始InterceptKeys

public static event Action HomePressed;
public static event Action EndPressed;

现在我们可以只触发这两个事件,而不是在适当的位置调用另一个类的方法。只需更换:

Countdown timer = new Countdown(); //creating new instance
if ((Keys)vkCode == Keys.Home)
{
    timer.StartTimer();
}
if ((Keys)vkCode == Keys.End)
{
    timer.StopTimer();
}

和:

if ((Keys)vkCode == Keys.Home)
{
    if(HomePressed != null) HomePressed();
}
if ((Keys)vkCode == Keys.End)
{
    if(EndPressed != null) EndPressed();
}

那么,现在InterceptKeys有两个事件,现在呢?现在我们去InterceptKeys初始化Form1并处理事件。在我们这样做之前,我们首先要抓住曾经在里面的所有东西,Countdown然后把它放进去Form1,只需移动整个东西。使用所有这些方法,我们可以做到这一点:

private void Form1_Load(object sender, EventArgs e)
{
    InterceptKeys.InterceptInit();
    InterceptKeys.HomePressed += ()=> StartTimer();
    InterceptKeys.EndPressed += ()=> StopTimer();
}

现在,只要按下这两个键中的一个,就会在此表单的现有方法上调用相应的方法。最重要的是,我们现在已经将所有操纵表单显示的代码移到了该表单的定义中,同时确保所有讨厌的键盘钩子都在它自己的小世界中关闭。“分离关注”,一切都在它自己的适当位置。

附带说明一下,您应该真正创建私有字段,timer1counter不是公共字段。你没有公开使用它们,这很好,但你不想在未来这样做。您可以根据需要创建对这些字段提供有限访问的方法(这是您当前正在做的事情)。

只剩下一件事了。每次Home按下您都不想启动新计时器的机会。旧计时器仍然存在,因此您将拥有两个、三个或更多计时器。更有可能的是您只想重新启动现有的计时器。这很容易做到。

StartTimer你不能创建一个新的计时器,而是操纵现有的计时器。从它的身体中移除所有东西,除了:

timer1.Start();
changeText(counter.ToString());

然后只需Timer在首次加载表单时创建和配置:

private void Form1_Load(object sender, EventArgs e)
{
    timer1 = new System.Windows.Forms.Timer();
    timer1.Tick += new EventHandler(timer1_Tick);
    timer1.Interval = 1000; // 1 second

    InterceptKeys.InterceptInit();
    InterceptKeys.HomePressed += ()=> StartTimer();
    InterceptKeys.EndPressed += ()=> StopTimer();
}
于 2013-02-07T16:32:14.660 回答