ユーザーアクションログを簡素化する







以前の記事で、私たちはすばらしい善行を行いました。プログラムクラッシュの先史時代を自動的に収集し、クラッシュレポートとともに送信する方法を学びました。 WinFormsWPFASP、Javascriptの場合 。 次に、これらすべての情報を消化可能な形式で表示する方法を学習します。



まだ足りないものを見つけて、ログを注意深く読み、すべてが十分に詳細に書かれているように見えます。 おそらくあまりにも詳細です。 直接何らかのデバッグログ。 このことも間違いなく便利ですが、少し違うものが欲しかったです。 プログラムがクラッシュする前にユーザーが何をしていたかを知りたかった。 再生する手順。 開発者への手紙の人は、人間の言語で自分の行動をどのように説明しますか? おそらく次のようなもの:





«Edit Something»

«Edit»

«Name»



Enter

-








私たちはどうですか? 実際、同じように見えますが、「不必要な」詳細がたくさんあります。マウスをクリックする、マウスを放す、ボタンを押す、記号を入力する、ボタンを放すなどです。



ページに表示する直前に、サーバー側でログをより便利な形式に変換してみましょう。 元のログに対して1つずつ設定し、何が起こるかを確認するいくつかの「単純化」を実装しています。



マウスから始めましょう。

これは、WinFormsプラットフォームのマウスの左ボタンでダブルクリックすると、ログに表示される方法です。







簡単に確認できます。イベントのシーケンスでdown / up / doubleClick / upを探し、1つのdoubleClickエントリに置き換えます。 同時に、見つかったシーケンスですべてのイベントが同じマウスボタン(この例では左)を持ち、すべてのイベントが同じウィンドウで発生することを確認する必要があります。 また、誤解を避けるために、4つのイベントすべての座標がSystemInformation.DragSizeの正方形サイズを超えないことを確認することをお勧めします。 サーバーでこれを行います。クライアントマシンからDragSizeを適切に使用する必要があります。 実際には、それらが異なる状況を捉えることに成功したとしても、これはほとんど効果がありません。 したがって、これを意識的に無視し、固定値を使用します。



クライアントからマウス座標を送信する必要があります。



 void AppendMouseCoords(ref Message m, Dictionary<string, string> data) { data["x"] = GetMouseX(m.LParam).ToString(); data["y"] = GetMouseY(m.LParam).ToString(); } int GetMouseX(IntPtr param) { int value = param.ToInt32(); return value & 0xFFFF; } int GetMouseY(IntPtr param) { int value = param.ToInt32(); return (value >> 16) & 0xFFFF; }
      
      





次の「単純化」はシングルクリックを処理します。







すべてはダブルクリックと同じで、よりシンプルで短いだけです。 忘れてはならない唯一のことは、最初にすべてのダブルクリックを処理する必要があるということです。



キーボード入力に移りましょう。



英数字キーを1回押す:







一見、すべてはマウスの場合と同じですが、どのような問題が発生する可能性がありますか? ここには、 WM_KEYDOWNWM_CHAR 、およびWM_KEYUPの 3つのイベントが記録されています。 3つのエントリすべてが1つのイベント(Fボタンを押す)に関連していることを理解するために、共通点は何ですか? また、WM_KEYDOWNとWM_KEYUPには現在の入力言語に依存しない仮想キーコードが含まれているため、一般的にはビット16〜23のスキャンコードしかありません。 ただし、WM_CHARには、押されたキー(またはキーの組み合わせ、またはIME入力シーケンス)に対応するUnicode文字が既に含まれています。 パスワードを入力するときにスキャンコードを非表示にすることを忘れないで、スキャンコードもクライアントから送信する必要があります。



 void ProcessKeyMessage(ref Message m, bool isUp) { // etc data["scanCode"] = maskKey ? "0" : GetScanCode(m.LParam).ToString(); // etc } int GetScanCode(IntPtr param) { int value = param.ToInt32(); return (value >> 16) & 0xFF; }
      
      





さて、 ガスを消し、ストーブからケトルを外し、水を注ぎ、問題をすでに解決済みのものに減らします。マウスについては、3つのイベントを1つにまとめることができます。 「ええ、とても大きいですが、それでもおとぎ話を信じています!」-現実を言いました:







クイックタイピングでは、keyUpは対応するkeyDownとkeyPressにわずかに遅れることがあります。 したがって、特定のタイプの3つの連続したレコードのみを検索することはできません。 見つかった開始点(keyDown)から適切なkeyPressを検索し、次にkeyUpを検索する必要があります。 リストの最後を検索しますか? すぐに二次的な複雑さ、こんにちは、ブレーキです! はい。英数字以外のキーが押された場合、keyPressイベントはまったく発生しません。 したがって、最初のレコードから一定数のレコードを探すだけで、常識に基づいて番号を選択し、必要に応じて修正します。 キーごとに15、2〜3レコード、これは5〜8のキーを同時に押して離さないことを意味します。 通常の「民間人」の入力では十分すぎるほどです。



そのため、個々の文字(char型)を入力する前に個々のキーストロークが折りたたまれました。 これで、複雑なタイプの文字シーケンスを1つの大きなタイプのテキストに結合することができます。 連続性については、おそらく文字列の前後、中、後に興奮しましたが、Shiftキーを押して放すことは非常に受け入れられます。これは、イベントチェーンを最小化するときに考慮する必要があります。



英数字入力により、すべてを最小化できるようになり、残りのkeyDownとkeyUpのペアは、そこに大きな驚きはないと予想されます。



他に何が残っていますか? フォーカス



マウスによるフォーカスの変更:







トリプルレコードを検索して1つに置き換え、マウス座標の対応を制御します。



キーボードからフォーカスを変更:







マウスに非常に似ていますが、フォーカスを失うウィンドウにkeyDownが来て、それを受け取るウィンドウにkeyUpが来るという事実に固執しないことが重要です。



それでは、すべてをまとめて見てみましょう。



それは:







次のようになりました:







彼らが言うように、違いを感じてください。 情報は同じですが、はるかにコンパクトに設計されており、より自然に読むことができます。



WinFormsでの最初の近似では終了しました。 WPFに渡します。



ある程度、WPFはWinFormsに似ており、上記のルールのほとんどすべてを使用できます。 しかし、学校の数学のコースから思い出すように、典型的な方法で問題を解決するには、まずこの方法にそれをもたらす必要があります。 入力にあるものを見てみましょう。







各アクションにいくつかのログがあり、通常のマウスダウンイベントは4つのエントリに変わります。これは、各コントロールの子孫からウィンドウからボタン自体にビジュアルツリーを介して送信されます。 このようなパスのないかなり複雑なUIでは、クリックが発生した場所を正確に理解することは困難です。 ただし、このようなパスはログの外観をかなり混乱させ、ログを読む意欲を失わせます。



議論の結果、最良の解決策は、このログのリストを最後の1つに減らし、オプションですべてを表示できるようにすることであるという結論に達しました。 なぜ後者なのか? なぜなら、ボタンをクリックすることに関するメッセージは、ウィンドウをクリックすることよりも有用だからです。



しかし、あなたの前にあるアクションに関連するログのブロックがあることをどのように理解するのでしょうか? しかし、基本的に、1つのアクションからのすべてのイベントは同じEventArgsオブジェクトを受け取ります。 したがって、引数の各オブジェクトに一意の識別子を割り当てて、ログに書き込みます。



 class PreviousArgs { public EventArgs EventArgs { get; set; } public string Id { get; set; } } PreviousArgs previousArgs = null; short currentId = 0; Dictionary<string, string> CollectCommonProperties(FrameworkElement source, EventArgs e) { Dictionary<string, string> properties = new Dictionary<string, string>(); properties["Name"] = source.Name; properties["ClassName"] = source.GetType().ToString(); if(previousArgs == null) { previousArgs = new PreviousArgs() { EventArgs = e, Id = (currentId++).ToString("X") }; } else { if(e == null || !Object.ReferenceEquals(previousArgs.EventArgs, e)) { previousArgs = new PreviousArgs() { EventArgs = e, Id = (currentId++).ToString("X") }; } } properties["#e"] = previousArgs.Id; }
      
      





その後、サーバーで、最後のイベントを除く、1つのイベントに関連するすべてのログを破棄します。 取得するもの:







さて、ログサイズは大幅に削減され、現在はWinFormsとほぼ同じです。つまり、上記のルールを適用することができます。



マウスクリックを最小化するためのルールを取得するには、十分な座標コレクションがありません。 MouseEventArgs.GetPositionメソッドを使用してルート要素に相対的な座標を取得し、 Visual.PointToScreenを使用してウィンドウに相対的な座標を取得します。



 void CollectMousePosition(IDictionary<string, string> properties, FrameworkElement source, MouseButtonEventArgs e) { IInputElement inputElement = GetRootInputElement(source); if(inputElement != null) { Point relativePosition = e.GetPosition(inputElement); properties["x"] = relativePosition.X.ToString(); properties["y"] = relativePosition.Y.ToString(); if(inputElement is Visual) { Point screenPosition = (inputElement as Visual).PointToScreen(relativePosition); properties["sx"] = screenPosition.X.ToString(); properties["sy"] = screenPosition.Y.ToString(); } } } IInputElement GetRootInputElement(FrameworkElement source) { return GetRootInputElementCore(source, null); } IInputElement GetRootInputElementCore(FrameworkElement source, IInputElement lastInputElement) { if(source is IInputElement) lastInputElement = source as IInputElement; if(source != null) { return GetRootInputElementCore(source.Parent as FrameworkElement, lastInputElement); } return lastInputElement; }
      
      





これで、クリックとダブルクリックを最小限に抑えることに成功しました。



次に、フォーカスの移動を見てみましょう。 WPFのWinFormsとは異なり、GotFocusとLostFocusの2つのイベントを使用し、2つのログエントリがあります。 まあ、それは大丈夫です、最小化するとき、FocusChangedまたはGotとLostフォーカスイベントのペアのいずれかが検索されるようにロジックを修正してください。



原則として、WPFを使用してこれを完了しました。 残念ながら、TextInputイベントはキーボードから完全に引き裂かれており、スキャンコードに類似するものがないため、テキスト入力を削減することはできません。 その結果、この外観を実現することができました。







すべてが最初からより便利で、明確で、はるかに視覚的です。



これで、パンくずリストに関する一連の記事を終了します。 開発者として、この機能に関するご意見をお寄せいただければ幸いです。 または、Logifyが完全に人間の言語で話すようにBradcrambsログを単純化する方法に関するアイデア。



そして、もちろん、これは私たちのチームからの最後の記事ではありません。 興味のあることについてお気軽にお問い合わせください。すべての質問にお答えします。 そして、それらの最も興味深いものが新しい記事の基礎になるかもしれません。



All Articles