0

C# プログラムでいくつかの問題が発生しました。特定のキーが押されたときに開始されるストップウォッチ(特定の値からのカウントダウン)を作成したい。キーを処理するには、低レベルのキーボード フックを使用します。しかし、このクラスには静的メソッドがあるため、静的ではない別のクラスからメソッドを呼び出したい場合は、新しいインスタンスを作成する必要があります。カウントダウンで、ティック(秒)ごとTextに要素のプロパティを変更したい。問題は、静的メソッドでクラスの新しいインスタンスを作成する必要があるときに、(クラス内の) すべてのティックTextBoxのプロパティを変更する方法です。そのため、 は以前の に応答しなくなります。私のコードは完全に正常に動作し、キーが認識され、タイマーがカウントダウンし、秒の値が個別に表示されますTextBoxCountdownCountdownTextBoxTextBoxMessageBox'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正直なところ、クラスが存在するべきではないとさえ思います。そのメソッドは、他の 2 つのクラスのいずれかに属する必要があります。

から見ていきましょうInterceptKeys。クラスの 90% が単にフックを作成し、それが「関心のある」イベントであるかどうかを判断していることがわかります。次に、関心のあることが起こったときに必要な処理を実行するためのコードはほんの少し (その 10%) しかありません。

「関心の分離」に対処するために、これをより効果的に処理できる方法があります。このInterceptKeysクラスは、キーボード フックを設定し、重要でないものを除外する定型コードのみを処理する必要があります。コードの最後の 10% をこのクラスから移動したいと考えています。イベントはこれを行うための優れた方法です。論理的には、ここで 2 つの「イベント」が発生します。1 つはホーム キーが押されたとき、もう 1 つは終了キーが押されたときです。そのため、次の 2 つのイベントを 内に作成することから始めますInterceptKeys

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

これで、適切な場所で別のクラスのメソッドを呼び出す代わりに、これら 2 つのイベントを発生させることができます。置き換えるだけです:

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();
}

では、InterceptKeys2 つのイベントがあります。InterceptKeys次に、が初期化された場所に移動しForm1、イベントを処理します。これを行う前に、最初に の中にあったすべてのものをつかみCountdown、それを正しく入れてForm1、全体を移動します。これらすべてのメソッドを使用して、これを行うことができます。

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

これら 2 つのキーのいずれかが押されると、このフォームの既存のメソッドで適切なメソッドが呼び出されます。その上で、フォームの表示を操作しているすべてのコードをそのフォームの定義に移動し、その厄介なキーボード フックのすべてが独自の小さな世界でオフになっていることを確認しました。「関心の分離」、すべてが適切な場所にあります。

余談ですが、パブリック フィールドではなくtimer1、プライベート フィールドを実際に作成する必要があります。counterそれらを公に使用していないのは良いことですが、将来的には使用したくありません。必要に応じて、これらのフィールドへの制限付きアクセスを提供するメソッドを作成できます (これが現在行っていることです)。

残っているのは 1 つだけです。が押されるたびHomeに、新しいタイマーを開始したくない可能性があります。古いタイマーはまだ残っているので、タイマーは 2 つ、3 つ、またはそれ以上になります。より可能性が高いのは、既存のタイマーを再起動したいということです。これは簡単です。

では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 に答える