【问题标题】:C# Form modyfing in static methods静态方法中的 C# 表单修改
【发布时间】:2013-01-23 04:00:12
【问题描述】:

我的 C# 程序遇到了一些问题。我想创建秒表(从某个值开始倒计时),即在按下某个键时启动。为了处理键,我使用低级键盘钩子。但是这个类有静态方法,所以如果我想从不同的类调用一个方法,那不是静态的,我必须创建一个新实例。随着倒计时,我想每滴答(秒)更改TextBox 元素的Text 属性。问题是,当我必须在静态方法中创建Countdown 类的新实例时,如何在每个刻度(在Countdown 类中)更改TextBox 的属性,因此TextBox 将不再回复之前的TextBox。我的代码工作得很好,按键被识别,计时器正在倒计时并在单独的MessageBox'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();
        }

     }

  }

}

【问题讨论】:

  • 请务必显示(相关)代码。
  • 如果不查看您的代码,很难确切地了解问题可能是什么。您正在实施什么样的计时器?这可能是重复的:stackoverflow.com/questions/3959107/… 尝试使用 System.Windows.Forms.Timer 而不是 System.Timers.Timer 或 System.Threading.Timer。
  • 我已经添加了代码。感谢您的快速回复。 :)

标签: c# winforms static textbox instance


【解决方案1】:

所以,首先,您不希望 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 有两个事件,现在呢?现在我们转到InterceptKeysForm1 中初始化的位置并处理事件。在我们这样做之前,我们首先要获取曾经在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();
}

【讨论】:

  • 我绝对爱你! :) 非常感谢您的大力帮助!
  • @sobol6803 不客气。以后在进行 winform 编程时,请尝试应用这些相同类型的技术。
  • 老实说,这是我的第一个 c# 代码,基本上是第一个使用 OOP 的代码。我以前只用 C 和 PHP 写过一些东西。我现在将扩展计时器,因此倒计时的时间也将由热键设置。再次非常感谢您的帮助。 :)
  • @sobol6803 是的,我可以告诉你是新手,这就是为什么我花时间和精力彻底解释每个步骤,而不是仅仅发布代码块或链接,以帮助你能够将来重现这些步骤。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-07-01
  • 1970-01-01
  • 1970-01-01
  • 2019-10-11
  • 2011-01-17
  • 2011-05-30
  • 1970-01-01
相关资源
最近更新 更多