VB のたまご

作成日: 2016/08/22, 更新日: 2016/08/22


Friendly を使った WinForms アプリの自動テストを楽したい

  •  以前、uwscを使ったWinFormsアプリの自動テストを楽したいという記事を書いたのですが、 やっぱり Visual Studio 上でテストしたいですよね。

  •  そこで今回は、Friendly(フレンドリー) を使って WinForms な画面を操作しつつ、MSTest 形式のテストコードを書いてテストしようと思います。 Friendly というのは、Win32、WinForms、WPF 等の画面を操作することができるフレームワークのことです。

  •  ただし、タイトルにもある通り、普通に動かしてテストするのではなく、前回同様、簡単に実装できるように、 ラッパークラスを作って使用した版で動かしたいと思います。

  •  以降では、その話をしているわけですが、MSTest、Friendly、Ong.Friendly.FormsStandardControls の3つについて、既に知ってるよ&使い方わかるよ、という方であれば今回は話が早いかな、理解するのが容易かなと思います。 また、3つとも知らないという方も、もし興味があれば、ぜひこのまま読んでいただきたいと思っています。 そのために作成したのがこの記事みたいなものですので。

  •  ちなみに、MSTest はググればすぐに分かりますが、Friendly は、C#er さん向けのサイトしか見つけられなかったため、 VBer さん向けに紹介記事を作っておきたいとは思っていました。ただ、ここではその応用の話になるため、 そのうち、別記事として作ろうと思います。ただし、私自身まだ勉強中なので基礎レベルの記事になりそうです。 また、今回は、C# と VB のサンプルコードを載せています。

  • スポンサーリンク


Friendly をもっと簡単に、扱いやすく

  •  Friendly ではダイナミック型を使って対象の画面クラスを操作していきます( .NET Framework 4.0 以上の場合。それ以前の場合は、インデクサー経由で操作します)。 でもインテリセンスが効かないため、分かりやすい操作が出来る反面、ちょっと実装しにくいところがあります。 ただ、それでも満足していました。

  •  UI テスターは他に何があるのか調べていたら、FEST-Swing というツールにたどり着きました。 FEST-Swing は、Java 言語用の GUI ツールキットである Swing のテスティングツールなんだそうです。

  • VB だったらこう書くだろう的な操作イメージ、型推論に頼る版
  • Dim form = WindowFinder.FindFrame("Form1")
    form.TextBox("textBox1").ChangeText("hello, friendly!")
    form.Button("button1").Click()
    

  •  あ、これほしいwってすぐに思いました。【画面.コントロール.アクション()】という分かりやすい操作方法です。 この仕組み良いな、.net 版無いかな、と思い調べたところ探せませんでしたorz

  •  んじゃあ、作ったるわ~!

  •  ということで、本記事のネタが生まれたのでした。画面操作はあくまでサブであるべきで、メインはテストであるべきです。 操作用の実装にかかる時間が減り、テストコード作成に集中できるようになるように、作ってみたいと思います。

Friendly を隠蔽しちゃえ!

  •  今回の目標は、【Friendly を知らない人でも、最低限のお作法で画面操作できる】ようにすることにしました。 具体的な着眼点は、WindowsAppFriend から取得した画面と、Ong.Friendly.FormsStandardControls でラッピングしたコントロールを、自然な流れでシームレスにつなげることができれば実現できるのでは、 と考えました。上記サンプルソースみたいなやつですね。このシームレスに次につなげる仕組み、これはつまり、 画面クラスやコントロールクラスの型を意識しなくても操作(実装)ができる、ということにつながります。 同時に、インテリセンスの恩恵も受けることもできます。

  •  要するに、上のイメージみたいな操作をしたいよ、インテリセンスのサポートも受けたいよ。 両方叶えるには、上記着眼点を攻略する必要があるね。ということが分かりました。

  •  で、実現方法を考えたのですが、1.画面を見つける人、2.画面操作をサポートする人を担当者として作成することにしました。 1.があることで画面取得が容易になり、2.があることでコントロール取得と操作が容易になります。

画面を見つける人(FormFinder)

  • プロダクトプロセス中で開いている画面を基に、欲しい画面を探し出すのが仕事です。

  • C# コード
  • using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Diagnostics;
    using Codeer.Friendly.Dynamic;
    using Codeer.Friendly.Windows;
    using Codeer.Friendly.Windows.Grasp;
    using Codeer.Friendly;
    using Codeer.Friendly.Windows.NativeStandardControls;
    using Ong.Friendly.FormsStandardControls;
    using System.Windows.Forms;
    using System.Threading;
    
    namespace FriendlyTest5.CSharp
    {
    
        // 捕まえたプロセスを基に、取得したい画面を見つけ出して返却するのが仕事
        public static class FormFinder
        {
            
            public static FormOperator ActiveForm(WindowsAppFriend app)
            {
                dynamic form = null;
                while (form == null || ((AppVar)form).IsNull == true)
                {
                    form = app.Type<Form>().ActiveForm;
                    Thread.Sleep(100);
                }
                
                return new FormOperator(form);
    
            }
            
            public static FormOperator FindForm(WindowsAppFriend app, string controlName)
            {
                FormOperator result = null;
                while (true)
                {
                    Enumerate forms = new Enumerate(app.Type<Application>().OpenForms);
                    foreach (AppVar form in forms)
                    {
                        var name = (string)form["Name"]().Core;
                        if (name.Contains(controlName))
                        {
                            result= new FormOperator(form.Dynamic());
                            break;
                        }
                    }
    
                    if(result != null) { break; }
                    Thread.Sleep(100);
                }
                return result;
    
            }
    
            public static NativeMessageBox FindMessageBox(WindowsAppFriend app)
            {
                var dlgCore = WindowControl.FromZTop(app);
                var dlg = new NativeMessageBox(dlgCore);
                return dlg;
    
            }
    
        }
    }
    

  • VB コード
  • 
    Option Strict Off ' Dynamic を使いたいため
    
    Imports Codeer.Friendly
    Imports Codeer.Friendly.Dynamic
    Imports Codeer.Friendly.Windows
    Imports Codeer.Friendly.Windows.Grasp
    Imports Codeer.Friendly.Windows.NativeStandardControls
    Imports Ong.Friendly.FormsStandardControls
    Imports System.Threading
    Imports System.Windows.Forms
    
    ' 捕まえたプロセスを基に、取得したい画面を見つけ出して返却するのが仕事
    Public Class FormFinder
    
        Public Shared Function ActiveForm(ByVal app As WindowsAppFriend) As FormOperator
    
            Dim form As Object = Nothing
            While (form Is Nothing) OrElse (CType(CType(form, DynamicAppVar), AppVar).IsNull = True)
                form = app.Type(Of Form)().ActiveForm
                Thread.Sleep(100)
            End While
    
            Return New FormOperator(form)
    
        End Function
    
        Public Shared Function FindForm(ByVal app As WindowsAppFriend, ByVal controlName As String) As FormOperator
    
            Dim result As FormOperator = Nothing
            While True
    
                Dim dItems As DynamicAppVar = CType(app.Type(Of Application)().OpenForms, DynamicAppVar)
                Dim aItems As AppVar = CType(dItems, AppVar)
                Dim forms As Enumerate = New Enumerate(aItems)
    
                For Each form As AppVar In forms
    
                    Dim name As String = CType(form("Name")().Core, String)
                    If name.Contains(controlName) Then
                        result = New FormOperator(form.Dynamic())
                        Exit For
                    End If
    
                Next
    
                If result IsNot Nothing Then
                    Exit While
                End If
    
                Thread.Sleep(100)
            End While
    
            Return result
    
        End Function
    
        Public Shared Function FindMessageBox(ByVal app As WindowsAppFriend) As NativeMessageBox
    
            Dim dlgCore As WindowControl = WindowControl.FromZTop(app)
            Dim dlg As New NativeMessageBox(dlgCore)
            Return dlg
    
        End Function
    
    End Class
    
    

画面操作をサポートする人(FormOperator)

  •  指定画面内にある各コントロール操作を、扱いやすくサポートするのが仕事です。 操作画面を内部に持っていて、ここから取得してきます。

  • C# コード
  • using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Diagnostics;
    using Codeer.Friendly.Dynamic;
    using Codeer.Friendly.Windows;
    using Codeer.Friendly.Windows.Grasp;
    using Codeer.Friendly;
    using Codeer.Friendly.Windows.NativeStandardControls;
    using Ong.Friendly.FormsStandardControls;
    using System.Windows.Forms;
    
    namespace FriendlyTest5.CSharp
    {
    
        // 指定画面内にある各コントロール操作を、扱いやすくサポートするのが仕事
        public class FormOperator
        {
            // やりたい機能が提供されていない場合は、TargetForm を直接操作してください。
            // xxx.TargetForm.button1.PerformClick() とか。
            public dynamic TargetForm = null;
    
            public FormOperator(dynamic form)
            {
                TargetForm = form;
            }
    
            //public FormsButton Button(string name)
            //{  
            //    AppVar control = GetControl(name);
            //    return new FormsButton(control);
            //}
    
            //public Xxx1 Xxx2(string name) { return new Xxx1(GetControl(name)); }
            public FormsButton Button(string name) { return new FormsButton(GetControl(name)); }
            public FormsCheckBox CheckBox(string name) { return new FormsCheckBox(GetControl(name)); }
            public FormsCheckedListBox CheckedListBox(string name) { return new FormsCheckedListBox(GetControl(name)); }
            public FormsComboBox ComboBox(string name) { return new FormsComboBox(GetControl(name)); }
            public FormsDataGridView DataGridView(string name) { return new FormsDataGridView(GetControl(name)); }
            public FormsDateTimePicker DateTimePicker(string name) { return new FormsDateTimePicker(GetControl(name)); }
            public FormsLinkLabel LinkLabel(string name) { return new FormsLinkLabel(GetControl(name)); }
            public FormsListBox ListBox(string name) { return new FormsListBox(GetControl(name)); }
            public FormsListView ListView(string name) { return new FormsListView(GetControl(name)); }
            public FormsMaskedTextBox MaskedTextBox(string name) { return new FormsMaskedTextBox(GetControl(name)); }
            public FormsMonthCalendar MonthCalendar(string name) { return new FormsMonthCalendar(GetControl(name)); }
            public FormsNumericUpDown NumericUpDown(string name) { return new FormsNumericUpDown(GetControl(name)); }
            public FormsProgressBar ProgressBar(string name) { return new FormsProgressBar(GetControl(name)); }
            public FormsRadioButton RadioButton(string name) { return new FormsRadioButton(GetControl(name)); }
            public FormsRichTextBox RichTextBox(string name) { return new FormsRichTextBox(GetControl(name)); }
            public FormsTabControl TabControl(string name) { return new FormsTabControl(GetControl(name)); }
            public FormsTextBox TextBox(string name) { return new FormsTextBox(GetControl(name)); }
            public FormsToolStrip ToolStrip(string name) { return new FormsToolStrip(GetControl(name)); }
            public FormsTrackBar TrackBar(string name) { return new FormsTrackBar(GetControl(name)); }
            public FormsTreeView TreeView(string name) { return new FormsTreeView(GetControl(name)); }
    
            private AppVar GetControl(string controlName)
            {
                AppVar form = (AppVar)TargetForm;
                AppVar control = form[controlName]();
                return control;
            }
    
        }
    }
    

  • VB コード
  • 
    Option Strict Off ' Dynamic を使いたいため
    
    Imports Codeer.Friendly
    Imports Codeer.Friendly.Dynamic
    Imports Codeer.Friendly.Windows
    Imports Codeer.Friendly.Windows.Grasp
    Imports Codeer.Friendly.Windows.NativeStandardControls
    Imports Ong.Friendly.FormsStandardControls
    
    ' 指定画面内にある各コントロール操作を、扱いやすくサポートするのが仕事
    Public Class FormOperator
    
        ' 実体は、DynamicAppVar 型
        ' やりたい機能が提供されていない場合は、TargetForm を直接操作してください。
        ' xxx.TargetForm.button1.PerformClick() とか。
        Public TargetForm As Object = Nothing
    
        Public Sub New(ByVal form As Object)
    
            TargetForm = form
    
        End Sub
    
        Public Function Button(ByVal name As String) As FormsButton
    
            Dim control As AppVar = GetControl(name)
            Return New FormsButton(control)
    
        End Function
    
        Public Function CheckBox(ByVal name As String) As FormsCheckBox
    
            Dim control As AppVar = GetControl(name)
            Return New FormsCheckBox(control)
    
        End Function
    
        Public Function CheckedListBox(ByVal name As String) As FormsCheckedListBox
    
            Dim control As AppVar = GetControl(name)
            Return New FormsCheckedListBox(control)
    
        End Function
    
        Public Function ComboBox(ByVal name As String) As FormsComboBox
    
            Dim control As AppVar = GetControl(name)
            Return New FormsComboBox(control)
    
        End Function
    
        Public Function DataGridView(ByVal name As String) As FormsDataGridView
    
            Dim control As AppVar = GetControl(name)
            Return New FormsDataGridView(control)
    
        End Function
    
        Public Function DateTimePicker(ByVal name As String) As FormsDateTimePicker
    
            Dim control As AppVar = GetControl(name)
            Return New FormsDateTimePicker(control)
    
        End Function
    
        Public Function LinkLabel(ByVal name As String) As FormsLinkLabel
    
            Dim control As AppVar = GetControl(name)
            Return New FormsLinkLabel(control)
    
        End Function
    
        Public Function ListBox(ByVal name As String) As FormsListBox
    
            Dim control As AppVar = GetControl(name)
            Return New FormsListBox(control)
    
        End Function
    
        Public Function ListView(ByVal name As String) As FormsListView
    
            Dim control As AppVar = GetControl(name)
            Return New FormsListView(control)
    
        End Function
    
        Public Function MaskedTextBox(ByVal name As String) As FormsMaskedTextBox
    
            Dim control As AppVar = GetControl(name)
            Return New FormsMaskedTextBox(control)
    
        End Function
    
        Public Function MonthCalendar(ByVal name As String) As FormsMonthCalendar
    
            Dim control As AppVar = GetControl(name)
            Return New FormsMonthCalendar(control)
    
        End Function
    
        Public Function NumericUpDown(ByVal name As String) As FormsNumericUpDown
    
            Dim control As AppVar = GetControl(name)
            Return New FormsNumericUpDown(control)
    
        End Function
    
        Public Function ProgressBar(ByVal name As String) As FormsProgressBar
    
            Dim control As AppVar = GetControl(name)
            Return New FormsProgressBar(control)
    
        End Function
    
        Public Function RadioButton(ByVal name As String) As FormsRadioButton
    
            Dim control As AppVar = GetControl(name)
            Return New FormsRadioButton(control)
    
        End Function
    
        Public Function RichTextBox(ByVal name As String) As FormsRichTextBox
    
            Dim control As AppVar = GetControl(name)
            Return New FormsRichTextBox(control)
    
        End Function
    
        Public Function TabControl(ByVal name As String) As FormsTabControl
    
            Dim control As AppVar = GetControl(name)
            Return New FormsTabControl(control)
    
        End Function
    
        Public Function TextBox(ByVal name As String) As FormsTextBox
    
            Dim control As AppVar = GetControl(name)
            Return New FormsTextBox(control)
    
        End Function
    
        Public Function ToolStrip(ByVal name As String) As FormsToolStrip
    
            Dim control As AppVar = GetControl(name)
            Return New FormsToolStrip(control)
    
        End Function
    
        Public Function TrackBar(ByVal name As String) As FormsTrackBar
    
            Dim control As AppVar = GetControl(name)
            Return New FormsTrackBar(control)
    
        End Function
    
        Public Function TreeView(ByVal name As String) As FormsTreeView
    
            Dim control As AppVar = GetControl(name)
            Return New FormsTreeView(control)
    
        End Function
    
        Private Function GetControl(ByVal controlName As String) As AppVar
    
            Dim dItem As DynamicAppVar = CType(TargetForm, DynamicAppVar)
            Dim form As AppVar = CType(dItem, AppVar)
            Dim control As AppVar = form(controlName)()
            Return control
    
        End Function
    
        Public Shared Function GetData(Of T)(ByVal targetControl As Object, ByVal memberName As String) As T
    
            Dim item1 = CType(targetControl, DynamicAppVar)
            Dim item2 = CType(item1, AppVar)
            Dim item3 = CType(item2(memberName)().Core, T)
            Return item3
    
        End Function
    
    End Class
    

    スポンサーリンク


いっちょ、テストしてみっか

  • 準備ができたので、テストをしてみます。 操作対象となる WinForms アプリは、足し算プログラムです。 NumericUpDown で数字を入力して、加算した結果をラベルとメッセージボックスで表示します。

  • イメージ
    Public Class Form1
    
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    
            Dim i1 As Decimal = NumericUpDown1.Value
            Dim i2 As Decimal = NumericUpDown2.Value
            Dim item As Decimal = i1 + i2
    
            Label4.Text = item.ToString()
            MessageBox.Show("答えは " & Label4.Text & " です。")
    
        End Sub
    
    End Class
    

  •  最初に、Visual Studio を開いて、単体テストプロジェクトを作成します。 次に、NuGet などで Friendly 関連をごそっと取ってきます。 3つ目に、上記2つのヘルパークラスを追加します。 4つ目に、以下のテストコードを書きます。

  • C# コード
  • using System;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using System.Diagnostics;
    using Codeer.Friendly.Dynamic;
    using Codeer.Friendly.Windows;
    using Codeer.Friendly.Windows.Grasp;
    using Codeer.Friendly;
    using Codeer.Friendly.Windows.NativeStandardControls;
    using Ong.Friendly.FormsStandardControls;
    using System.Windows.Forms;
    using System.Threading;
    
    namespace FriendlyTest5.CSharp
    {
        [TestClass]
        public class UnitTest1
        {
            private string exeFile = "WindowsApplication1.exe";
            private WindowsAppFriend app = null;
    
            [TestInitialize]
            public void TestInitialize()
            {
                // 実行中のプロセス一覧から操作対象プロセスを探して、見つけて操作する
                //app = new WindowsAppFriend(Process.Start(exeFile));
                Process.Start(exeFile);
                var target = Process.GetProcessesByName(exeFile.Replace(".exe", string.Empty))[0];
                app = new WindowsAppFriend(target);
            }
    
            [TestCleanup]
            public void TestCleanup()
            {
                CloseMainWindowIfExists();
                app.Dispose();
            }
    
            private void CloseMainWindowIfExists()
            {
                // 存在しないプロセスID を指定したときの GetProcessById メソッドの動作確認が未検証orz
                //Process.GetProcessById(app.ProcessId).CloseMainWindow();
    
                // テストメソッド側で、プロセスごと終了してしまった場合ても大丈夫なように、
                // 存在チェックしつつプロセス終了する。
                var processes = Process.GetProcesses();
                foreach (var process in processes)
                {
                    if (process.Id == app.ProcessId)
                    {
                        process.CloseMainWindow();
                        break;
                    }
                }
    
            }
    
            [TestMethod]
            public void TestMethod1()
            {
    
                // 操作が速すぎて見えないため、何かするたびに1秒待機しています。
    
                var form = FormFinder.FindForm(app, "Form1");
                Thread.Sleep(1000);
    
                form.NumericUpDown("NumericUpDown1").EmulateChangeValue(1);
                Thread.Sleep(1000);
    
                form.NumericUpDown("NumericUpDown2").EmulateChangeValue(2);
                Thread.Sleep(1000);
    
                // 次の画面が ShowDialog 系の場合、非同期処理で実行させないと、閉じるまでここで処理が止まってしまう
                form.Button("Button1").EmulateClick(new Async());
                Thread.Sleep(1000);
    
                // メッセージボックスはネイティブウィンドウなので別枠での取得
                var dlg = FormFinder.FindMessageBox(app);
                var actual = dlg.Message;
                var expected = "答えは 3 です。";
                dlg.EmulateButtonClick("OK");
    
                Assert.AreEqual(expected, actual, "計算結果が正しい結果のメッセージになっていること");
    
                // Label が無いので、ダイナミック型で取得
                actual = (string)form.TargetForm.Label4.Text;
                expected = "3";
    
                Assert.AreEqual(expected, actual, "計算結果が正しい値で表示されていること");
                
            }
    
        }
    }
    

  • VB コード
  • 
    Option Strict Off ' ダイナミック型を使って操作する場合は、コメントアウトを外します。
    
    Imports System.Text
    Imports Microsoft.VisualStudio.TestTools.UnitTesting
    Imports Codeer.Friendly
    Imports Codeer.Friendly.Dynamic
    Imports Codeer.Friendly.Windows
    Imports Codeer.Friendly.Windows.Grasp
    Imports Codeer.Friendly.Windows.NativeStandardControls
    Imports Ong.Friendly.FormsStandardControls
    Imports System.Threading
    Imports System.Windows.Forms
    
    <TestClass()>
    Public Class UnitTest1
    
        Private exeFile As String = "WindowsApplication1.exe"
        Private app As WindowsAppFriend = Nothing
    
        <TestInitialize()>
        Public Sub TestInitialize()
    
            ' 実行中のプロセス一覧から操作対象プロセスを探して、見つけて操作する
            ' app = New WindowsAppFriend(Process.Start(exeFile))
            Process.Start(exeFile)
            Dim target As Process = Process.GetProcessesByName(exeFile.Replace(".exe", String.Empty))(0)
            app = New WindowsAppFriend(target)
    
        End Sub
    
        <TestCleanup()>
        Public Sub TestCleanup()
    
            CloseMainWindowIfExists()
            app.Dispose()
    
        End Sub
    
        Private Sub CloseMainWindowIfExists()
    
            ' 存在しないプロセスID を指定したときの GetProcessById メソッドの動作確認が未検証orz
            ' Process.GetProcessById(app.ProcessId).CloseMainWindow()
    
            ' テストメソッド側で、プロセスごと終了してしまった場合ても大丈夫なように、
            ' 存在チェックしつつプロセス終了する。
            Dim processes As Process() = Process.GetProcesses()
            For Each p As Process In processes
    
                If p.Id = app.ProcessId Then
                    p.CloseMainWindow()
                    Exit For
                End If
    
            Next
    
        End Sub
    
        <TestMethod()>
        Public Sub TestMethod1()
    
            ' 操作が速すぎて見えないため、何かするたびに1秒待機しています。
    
            Dim form = FormFinder.FindForm(app, "Form1")
            Thread.Sleep(1000)
    
            form.NumericUpDown("NumericUpDown1").EmulateChangeValue(1)
            Thread.Sleep(1000)
    
            form.NumericUpDown("NumericUpDown2").EmulateChangeValue(2)
            Thread.Sleep(1000)
    
            ' 次の画面が ShowDialog 系の場合、非同期処理で実行させないと、閉じるまでここで処理が止まってしまう
            form.Button("Button1").EmulateClick(New Async)
            Thread.Sleep(1000)
    
            ' メッセージボックスはネイティブウィンドウなので別枠での取得
            Dim dlg = FormFinder.FindMessageBox(app)
            Dim actual As String = dlg.Message
            Dim expected As String = "答えは 3 です。"
            dlg.EmulateButtonClick("OK")
    
            Assert.AreEqual(expected, actual, "計算結果が正しい結果のメッセージになっていること")
    
            ' Label が無いので、ダイナミック型で取得
            ' ダイナミック型を使用する場合、冒頭の Option Strict Off を有効にします。
            actual = FormOperator.GetData(Of String)(form.TargetForm.Label4, "Text")
            expected = "3"
    
            Assert.AreEqual(expected, actual, "計算結果が正しい値で表示されていること")
    
        End Sub
    
    End Class
    

  •  5つ目に、ビルドを済ませて生成された dll と同じ場所に、操作対象 exe を配置します。 それでは、テストしてみます。 テスト中、ブレークポイントを張った箇所で止まりたい場合は、テストの実行ではなく、テストのデバッグから実行します。 さて、うまく画面操作ができたでしょうか?自動的に動いたでしょうか? うまく動いてくれればいいのですが。。。

サンプルコードのダウンロード

  • 上記サンプルは、以下のリンクからダウンロードできます。

  •  無料ダウンロードする(FriendlyTest5.zip, 52 KB)

  •  軽くしたいため、NuGet からの取得分を削除しています。 NuGet の復元をしてほしいので、ビルド時は、インターネットに接続してビルドしてください。

  •  テストする時は、捜査対象の WinForms アプリケーション( WindowsApplication1 )をビルドして、 できた exe ファイルを、テスト dll と同じ場所に配置してください。

おわりに

  •  ところでこのネタ、まだ完成していなくて、キーボード操作(特にファンクションキー押下のエミュレート)、 操作中の画面キャプチャと画像ファイル保存する機能の2つが全然できていませんorz。 作んなきゃ・・・、とりあえず今日は休もう。

  •  そういえば、ここでは標準コントロール向けのラッパー集を利用しましたが、Infragistics、GrapeCity などベンダー向けのラッパー集もあるそうです。こちらの方がみなさん使われているんでしょうね。 最後までこの記事を読んでいただき、ありがとうございました。

  • スポンサーリンク