20200715のC#に関する記事は9件です。

【UiPath】 UiPathを使いはじめての所感やメモ

はじめに

前回

オススメの書籍(ふつうのURL)
絶対失敗しない! ロボット1000体を導入してわかったRPA成功の秘訣

RPAで有名なUiPathが職場に導入されました。
しばらく触ってみた所感とよき使い方について書きます。


結論から申し上げますと

とっても便利・・・だけどなんか惜しい!
私の知らない使い方があるのかもしれないけど
とりあえず、そう思った。


良いところ

・動作が速い
・Elementの認識率が良い
・メタデータの管理にjsonが使える
・条件式などの文法にVBやC#が使われている
・Officeとの連携が強い


悪いところ

・動作が速すぎること
・OracleFrom等のElementの認識に一工夫必要なところ
・onchangeイベントを発火させるのにキーボード入力が必要なこと
・細かいアクティビティの仕様に気づかないとハマること
・文字列の変換処理に弱いこと
・過度な条件分岐を入れると複雑怪奇になること
・端末にインストールされていないと使えないこと*
・優れた機能があるのにも関わらず使われていない

*サーバ型ならリモート先で利用できるがリモート元では使用できない。


全体的な評価

Excel文化の会社にはとても良い薬になると思う。
どこで役に立つかわからないWindowsアプリケーションの
知識が役に立つこともあったりなかったり。。。


すっげー言いたいこと

サーバ上で処理できることを
わざわざローカルAndクライアントで
処理しようとするのは単なる無駄

なので
そこんところは製品を無理に利用せず
最適解を常に求めていきたい。


弱点

UiPath等のRPA製品は操作の対象がなければ
なんの役にも立たないこと。


惜しいところ

自分がもったいないなと思うことは
その操作の対象を作ることまで

一括で提供すれば、案件や業務の
コントロール性がめっちゃ上がるのに
ベンダー頼みでシステムを発注してしまうこと。


他、気になること

学習コストが高い。
コミュニティ版を使えば
金銭面の問題は解決できそうだけど

ActivityとかElementとかそのあたりの概念が
プログラマかつエンジニアの私にはきつかったな。


きつかったところ

主にElementは「JavaScript」などで言われている
Elementとは違うということ

アクティビティの仕様に気づかないとハマるということ


アクティビティの仕様

シナリオ作成をやっていくなかで
アクティビティをうまく活用しなければ
効率化は夢のまた夢


GetVisibleTextの仕様

表示されているテキストを取得すると聞いて
取得しようにも取得できなくてハマる。

実際には画面を最大化して
画面上に文字が表示されていないと
捉えられないという罠がある。


SetTextの仕様

SetTextはOracle Formでは機能しないこと
代わりにType Into アクティビティを使う。


プルダウンメニューのonchangeイベントが発火しない

これはイベントのメソッドを強制発火させれば
おおむねイケる感じはする。
JavaScriptのアクティビティで強制発火させるとイケる。
がしかし RPA太郎な皆さん、特に非ITな方には嫌われる書き方なので
キーボード入力で発火させる。結局は人間の動き


気づいたこと

人間的なシナリオの組み方を意識すると
結構、安定したシナリオ作成ができる。

例えば、マウスクリックはクリック対象にhoverしてから
クリックするとうまくクリックできたりする。
クリックシュミレートを活用したりするのも有効


クリックシュミレート

画面範囲外のクリック可能な要素もクリックできるように
してくれるすごいやつ

クリックがうまくいかない時はクリックシュミレートの
設定を見直したりするとうまくいく。


UiPathの根幹

dotNetFrameWorkですね。
Activityが利用するライブラリ群ですが
Microsoft Docsに仕様が書いてあります。
実際にアクティビティ本体を作る人もいるみたいですね。


まとめ

これからガンガンUiPathを使って効率化してきたい。
(本業のおまけ程度に)


おわり

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C#でTOML読み書き

■概要

C#でTOMLファイル読み書き

■環境

  • Windows 10
  • Visual Studio Community 2019 Version 16.6.4
  • .NET Framework 4.5.2/.NET Core 3.1

■準備

NuGetでNettをインストール

■画面イメージ

実行ファイルと同じ場所にTOML形式の設定ファイル作成
screenshot1.png

TomlTestCore.toml(設定ファイル)
Width = 650.0

[InputItems]
Item1 = "あいうえお"
Item2 = "@@@@@@@@@"
Item3 = "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"
Item4 = 12

■コード

.NET CoreでTomlTestCoreというプロジェクト名で作成。

MainWindow.xaml
<Window
    x:Class="TomlTestCore.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:TomlTestCore"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="MainWindow"
    Width="650"
    Height="250"
    ResizeMode="CanResizeWithGrip"
    mc:Ignorable="d">
    <Window.Resources>
        <!--  項目ラベルスタイル  -->
        <Style x:Key="ItemLabelStyle" TargetType="Label">
            <Setter Property="Width" Value="80" />
            <Setter Property="Margin" Value="5" />
        </Style>
        <!--  入力項目スタイル  -->
        <Style x:Key="ItemInputStyle" TargetType="TextBox">
            <Setter Property="Width" Value="300" />
            <Setter Property="Margin" Value="5" />
            <Setter Property="VerticalContentAlignment" Value="Center" />

            <EventSetter Event="GotFocus" Handler="ItemInput_GotFocus" />
        </Style>
        <!--  ボタン既定スタイル  -->
        <Style TargetType="Button">
            <Setter Property="Width" Value="100" />
            <Setter Property="Height" Value="35" />
            <Setter Property="Margin" Value="5,15,5,5" />
        </Style>
    </Window.Resources>

    <StackPanel Background="LavenderBlush" FocusManager.FocusedElement="{Binding ElementName=Item1}">
        <StackPanel Orientation="Horizontal">
            <Label
                Content="項目1(_1)"
                Style="{StaticResource ItemLabelStyle}"
                Target="{Binding ElementName=Item1}" />
            <TextBox x:Name="Item1" Style="{StaticResource ItemInputStyle}" />
        </StackPanel>

        <StackPanel Orientation="Horizontal">
            <Label
                Content="項目2(_2)"
                Style="{StaticResource ItemLabelStyle}"
                Target="{Binding ElementName=Item2}" />
            <TextBox x:Name="Item2" Style="{StaticResource ItemInputStyle}" />
        </StackPanel>

        <DockPanel LastChildFill="True">
            <Label
                Content="項目3(_3)"
                DockPanel.Dock="Left"
                Style="{StaticResource ItemLabelStyle}"
                Target="{Binding ElementName=Item3}" />
            <TextBox
                x:Name="Item3"
                Width="Auto"
                Style="{StaticResource ItemInputStyle}" />
        </DockPanel>

        <StackPanel Orientation="Horizontal">
            <Label
                Content="項目4(_4)"
                Style="{StaticResource ItemLabelStyle}"
                Target="{Binding ElementName=Item4}" />
            <TextBox
                x:Name="Item4"
                Width="40"
                MaxLength="3"
                Style="{StaticResource ItemInputStyle}" />
        </StackPanel>

        <StackPanel Orientation="Horizontal">
            <Button
                x:Name="SaveButton"
                Click="SaveButton_Click"
                Content="保存(_S)" />
            <Button
                x:Name="LoadButton"
                Click="LoadButton_Click"
                Content="読み込み(_L)" />
            <Button
                x:Name="ClearButton"
                Click="ClearButton_Click"
                Content="クリア(_C)" />
        </StackPanel>
    </StackPanel>
</Window>
MainWindow.xaml.cs
using Nett;
using System;
using System.Windows;
using System.Windows.Controls;

namespace TomlTestCore
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        /// <summary>
        /// 画面の値を設定ファイルに保存
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SaveButton_Click(object sender, RoutedEventArgs e)
        {
            var toml = Toml.Create();

            toml.Add("Width", Width);

            var inputItems = Toml.Create();
            inputItems.Add("Item1", Item1.Text);
            inputItems.Add("Item2", Item2.Text);
            inputItems.Add("Item3", Item3.Text);
            inputItems.Add("Item4", int.TryParse(Item4.Text, out int i) ? i : 99);

            toml.Add("InputItems", inputItems);

            Toml.WriteFile(toml, GetConfigFilePath());
        }

        /// <summary>
        /// 設定ファイルの値を画面に読み込み
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void LoadButton_Click(object sender, RoutedEventArgs e)
        {
            var toml = Toml.ReadFile(GetConfigFilePath());

            Width = toml.Get<double>("Width");

            var inputItems = toml.Get<TomlTable>("InputItems");
            Item1.Text = inputItems.Get<string>("Item1");
            Item2.Text = inputItems.Get<string>("Item2");
            Item3.Text = inputItems.Get<string>("Item3");
            Item4.Text = inputItems.Get<int>("Item4").ToString();
        }

        /// <summary>
        /// 画面クリア
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ClearButton_Click(object sender, RoutedEventArgs e)
        {
            Item1.Clear();
            Item2.Clear();
            Item3.Clear();
            Item4.Clear();
            Item1.Focus();
        }

        /// <summary>
        /// テキストボックスフォーカス取得時
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ItemInput_GotFocus(object sender, RoutedEventArgs e)
        {
            // 全選択
            (sender as TextBox).SelectAll();
        }

        /// <summary>
        /// 設定ファイルのフルパスを取得
        /// </summary>
        /// <returns>設定ファイルのフルパス</returns>
        private string GetConfigFilePath()
        {
            // 実行ファイルのフルパスを取得
            string appFilePath = System.Reflection.Assembly.GetEntryAssembly().Location;

            // 実行ファイルのフルパス末尾(拡張子)を変えて返す
            return System.Text.RegularExpressions.Regex.Replace(
                appFilePath,
                "\\.exe|dll$",
                "toml",
                System.Text.RegularExpressions.RegexOptions.IgnoreCase);
        }

    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[WinForm(unsafe)] USBカメラの列挙

public unsafe void ListDevices()
{
    AVFormatContext* camera;
    AVDictionary* option = null;

    ffmpeg.av_log_set_level(ffmpeg.AV_LOG_VERBOSE);
    av_log_set_callback_callback logCallback = (p0, level, format, vl) =>
    {
        if (level > ffmpeg.av_log_get_level()) return;

        var lineSize = 1024;
        var lineBuffer = stackalloc byte[lineSize];
        var printPrefix = 1;
        ffmpeg.av_log_format_line(p0, level, format, vl, lineBuffer, lineSize, &printPrefix);
        var line = Marshal.PtrToStringAnsi((IntPtr)lineBuffer);
        Console.Write(line);
    };
    ffmpeg.av_log_set_callback(logCallback);

    ffmpeg.avdevice_register_all();
    ffmpeg.av_dict_set(&option, "list_devices", "true", 0);
    ffmpeg.avformat_open_input(&camera, null, ffmpeg.av_find_input_format("dshow"), &option);
}

FFmpeg.AutoGen @Ruslan-B

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AR顔認証打刻アプリを作る

はじめに

皆さん、仕事の日は毎日打刻していると思いますが。
正直めんどくさいですよね。
それを解決するには、楽にできる方法を考えるのが普通だと思っていましたが。
この記事を読んで、気分を上げる打刻という解決策があるということを教わり、便乗してVRがあるならARもありだろうということで、作ろうと思いました。

作ったもの

動画はこちらです。

追記
ビルドすることができました。

https://twitter.com/akomekagome/status/1283239374348406785

あれARは?と思ったと思いますが
Macが不調でスマホにビルドすることができませんでした...
後日、ビルド出来たら上げ直したいと思います。

コードをあげておきます。(github)

https://github.com/akomekagome/ARAttendanceManagementScript

使用ライブラリ

FreeeForUnity
Unirx
Unitask
Zenject
Dotween
AWS for .net
OVRLipsync
OpenCV + Unity

システムの流れ

1.カメラの画像からOpenCV + Unityで顔を検出
2.先ほどの画像をAWS Rekognitionで顔を照合
3.取得した顔情報から、Freeeの従業員データーと紐づけし、打刻を行う

Freeeへの接続

接続は先ほどの記事を書いたtoofusanさんのコードを使わせていただきました。

https://github.com/toofusan/FreeeForUnity

使用するには、client_id, client_secret,code, access token, authorizationCode, employeeIdが必要になります。
ただ、employeeIdは初期のセットアップで取得できないので、個別で取得する必要があるのですが、一番簡単な方法として
Free人事労務フリー/勤怠/従業員名
でアクセスしたurlが

https://p.secure.freee.co.jp/employees#/employeeId/年/月/

となっているので、ここから取得できます。

カメラの画像をOpenCV + Unityで顔を検出

AWS Rekognitionも無料ではないので、できるだけ費用を抑えるためカメラに顔が映ったときだけ顔照合しています。
OpenCVでデフォルトで入っているカスケード型分類器を使って、顔認識を行うのですが、Unityに持っていかないと使えないので、StreamingAssetsに持っていく必要があります。

OpenCvGateway.cs
public class OpenCvGateway
{
    public bool DetectFaceInImage(Mat mat, string cascadePath)
    {
        var cascade = new CascadeClassifier();
    // ここでカスケード型分類器のパスを指定
        cascade.Load(cascadePath);

        var faces = cascade.DetectMultiScale(mat, 1.1, 3, 0, new Size(20, 20));

        Debug.Log(faces.Length);
        return faces.Length > 0;
    }
}

AWS Rekognitionで顔認証 

通常UnityからAWSを使う場合は、AWS for Untiyから使用するのが普通だと思いますが、今回使うAWS Rekognitionが非対応だったため、やむを得ずaws for .netのnugetをunityに持ってきました。
ただ今思えば、AWS Lambaは使えるので、そちらの方でAWS Rekognitionは使ったほうがいい気がします。(未検証)
方法としては、nugetの拡張子をzipに変更し、中身から自分が欲しい.netのバージョンのdllを探し、UnityのPluginフォルダーに突っ込めば、使用できます。nugetは以下から取得できます。

https://www.nuget.org/packages/AWSSDK.Rekognition/3.5.0-beta

使い方は以下が参考になります。

https://docs.aws.amazon.com/ja_jp/sdk-for-net/v3/developer-guide/quick-start.html

AmazonRekognitionへの接続は以下のような形です。

AmazonRekognitionGateway.cs
    public class AmazonRekognitionGateway
    {
        AmazonRekognitionClient rekoClient;

        public AmazonRekognitionGateway(CognitoAWSCredentials credentials)
        {
            rekoClient = new AmazonRekognitionClient(credentials);
        }

        public AmazonRekognitionGateway(string keyId , string secretKey)
        {
            rekoClient = new AmazonRekognitionClient(keyId, secretKey, AwsSettings.Region);
        }

        public AmazonRekognitionGateway(AWSAuthData awsAuthData)
        {
            rekoClient = new AmazonRekognitionClient(awsAuthData.keyId, awsAuthData.secretKey);
        }

        public async UniTask<List<string>> AuthenticateFaceAsync(MemoryStream stream, string collectionId, CancellationToken token = default)
        {
            var image = new Image()
            {
                Bytes = stream
            };
            return await AuthenticateFaceAsync(image, collectionId, token);
        }

        private async UniTask<List<string>> AuthenticateFaceAsync(Image image, string collectionId, CancellationToken token = default)
        {
            var searchFacesByImageRequest = new SearchFacesByImageRequest()
            {
                CollectionId = collectionId,
                Image = image,
                FaceMatchThreshold = 90f,
                MaxFaces = 1
            };

            try
            {
                var searchFacesByImageResponse = await rekoClient.SearchFacesByImageAsync(searchFacesByImageRequest, token);

                var faceIds = searchFacesByImageResponse.FaceMatches
                    .Select(x => x.Face.FaceId)
                    .ToList();

                DebugExtensions.DebugShowList(faceIds);

                return faceIds;
            }
            catch (Exception e)
            {
                Debug.Log(e);
                return default;
            }
        }
    }

AWS for .netでAmazonRekognitionを紹介している記事が日本だとないので、知見を共有すると、カメラから取得したTexture2Dをjpgのbyte配列に変換し、それをMemoryStreamに変換することによって、Amazon S3を介することなく顔照合が行えます。(料金が浮く)

FaceRecognitionProvider.cs
       private async UniTask<List<string>> AuthenticateFace(Mat mat, CancellationToken token = default)
        {
            var tex = webCamClient.GetTexture2D();
            var jpgBytes = tex.EncodeToJPG();
            Destroy(tex);
            var stream = new MemoryStream(jpgBytes);

            return await rekoGate.AuthenticateFaceAsync(
                stream,
                AmazonRekognitionSettings.FaceCollectionId,
                token);
        }

FreeeのAPIから打刻を行う

AmazonRekognitionは顔がFaceId(文字列)と結びついているので、employeeIdを紐づけしておき、打刻を行います。

以下のコードでまず、打刻可能なタイプを取得します

FreeeEventHandler.cs
public void GetTimeClocksAvailableTypes(int employeeId, Client.Callback callback = default) 
{
    var endpoint = "https://api.freee.co.jp/hr/api/v1/employees/" + employeeId +
                   "/time_clocks/available_types";
    var parameter = new Dictionary<string, string>
    {
        {"company_id", company_id.ToString()}
    };
    StartCoroutine(client.Get(endpoint, parameter, callback));
}

それをもとに打刻を行います。

FreeeEventHandler.cs
        public void PostTimeClocks(string type, int employeeId)
        {
            var endpoint = "https://api.freee.co.jp/hr/api/v1/employees/" + employeeId + "/time_clocks";
            var p = TimeClockRequestJson(type, DateTime.Now);
            StartCoroutine(client.Post(endpoint, p, OnPostTimeClocks));
        }

        void OnPostTimeClocks(bool success, string response)
        {
            if (!success)
            {
                var msg = JsonUtility.FromJson<Message>(response).message;
                return;
            }

            var tc = JsonUtility.FromJson<PostTimeClocksResponse>(response).employee_time_clock;
            _postTimeClockSubject.OnNext((FreeeType)Enum.Parse(typeof(FreeeType), tc.type));
        }

OVRLipsyncでリップシンクする

Oculusが作成したOVRLipsyncを使用します。Vtuberとかがよく使ってる口パクできるやつです。
といっても使い方は、とても簡単で使用したいキャラクターにAudioSource, OVRLipSyncContext, OVRLipContextMorphTargetをアタッチするだけです。
BlendShapesがあるSkinned Mesh Renderが必要になります。

それぞれ
aa -> あ
E -> え
ih -> い
oh -> お
ou -> う
という感じに、紐ずいています

2020-07-15 (2).png

まとめ

中途半端な形になってしまい申し訳ないです。
原因を見つけ次第更新したいと思います。
作業中、Unityちゃんの「進捗どうですか?」がぐさぐさ刺さりました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[WinForm(unsafe)] USBカメラの生データを取得する

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

using FFmpeg.AutoGen;

using OpenCvSharp;
using OpenCvSharp.Extensions;

namespace FFmpegAutoGenApp
{
    public partial class FormMain : Form
    {
        private CancellationTokenSource _cancel;

        public FormMain()
        {
            InitializeComponent();
        }

        public unsafe void FFmpeg_OpenCV(CancellationToken token)
        {
            AVFormatContext *camera;
            AVPacket packet;

            ffmpeg.avdevice_register_all();
            ffmpeg.avformat_open_input(&camera, "video=YOUR-WEBCAM", ffmpeg.av_find_input_format("dshow"), null);
            ffmpeg.av_init_packet(&packet);

            var codec = camera->streams[0]->codec;
            Mat dest = new Mat(codec->height, codec->width, MatType.CV_8UC3);

            while (token.IsCancellationRequested == false && ffmpeg.av_read_frame(camera, &packet) >= 0)
            {
                Mat source = new Mat(codec->height, codec->width, MatType.CV_8UC2, (IntPtr)packet.data);
                Cv2.CvtColor(source, dest, ColorConversionCodes.YUV2BGR_YUY2);

                Invoke(new Action(() =>
                {
                    pboxView.Image = BitmapConverter.ToBitmap(dest); 
                    pboxView.Update();
                }));

                ffmpeg.av_packet_unref(&packet);
            }

            ffmpeg.avformat_close_input(&camera);
        }

        public unsafe void Only_FFmpeg(CancellationToken token)
        {
            AVFormatContext* camera;
            AVPacket packet;

            ffmpeg.avdevice_register_all();
            ffmpeg.avformat_open_input(&camera, "video=YOUR-WEBCAM", ffmpeg.av_find_input_format("dshow"), null);
            ffmpeg.av_init_packet(&packet);

            var codec = camera->streams[0]->codec;
            var dataSize = ffmpeg.av_image_get_buffer_size(AVPixelFormat.AV_PIX_FMT_BGR24, codec->width, codec->height, 1);
            var dataBGR = ffmpeg.av_malloc((ulong)dataSize);
            var sws = ffmpeg.sws_getContext(codec->width, codec->height, codec->pix_fmt, codec->width, codec->height, AVPixelFormat.AV_PIX_FMT_BGR24, ffmpeg.SWS_FAST_BILINEAR, null, null, null);

            while (token.IsCancellationRequested == false && ffmpeg.av_read_frame(camera, &packet) >= 0)
            {
                var linesize = ffmpeg.av_image_get_linesize(codec->pix_fmt, codec->width, 0);
                var linesizeBGR = ffmpeg.av_image_get_linesize(AVPixelFormat.AV_PIX_FMT_BGR24, codec->width, 0);
                ffmpeg.sws_scale(sws, new[] { packet.data }, new[] { linesize }, 0, codec->height, new[] { (byte*)dataBGR }, new[] { linesizeBGR });

                Invoke(new Action(() =>
                {
                    pboxView.Image = new Bitmap(codec->width, codec->height, linesizeBGR, PixelFormat.Format24bppRgb, (IntPtr)dataBGR);
                    pboxView.Update();
                }));

                ffmpeg.av_packet_unref(&packet);
            }

            ffmpeg.av_free(dataBGR);
            ffmpeg.sws_freeContext(sws);
            ffmpeg.avformat_close_input(&camera);
        }

        private void btnRun_Click(object sender, EventArgs e)
        {
            _cancel = new CancellationTokenSource();
            var token = _cancel.Token;

            Task.Run(() => FFmpeg_OpenCV(token), token);
            //Task.Run(() => Only_FFmpeg(token), token);
        }

        private void btnStop_Click(object sender, EventArgs e)
        {
            _cancel.Cancel();
        }
    }
}

FFmpeg.AutoGen @Ruslan-B
OpenCvSharp @shimat

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[WinForm(unsafe)] USBカメラの生データを取得する

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

using FFmpeg.AutoGen;

using OpenCvSharp;
using OpenCvSharp.Extensions;

namespace FFmpegAutoGenApp
{
    public partial class FormMain : Form
    {
        private CancellationTokenSource _cancel;

        public FormMain()
        {
            InitializeComponent();
        }

        public unsafe void FFmpeg_OpenCV(CancellationToken token)
        {
            AVFormatContext *camera;
            AVPacket packet;

            ffmpeg.avdevice_register_all();
            ffmpeg.avformat_open_input(&camera, "video=YOUR-WEBCAM", ffmpeg.av_find_input_format("dshow"), null);
            ffmpeg.av_init_packet(&packet);

            var codec = camera->streams[0]->codec;
            Mat dest = new Mat(codec->height, codec->width, MatType.CV_8UC3);

            while (token.IsCancellationRequested == false && ffmpeg.av_read_frame(camera, &packet) >= 0)
            {
                Mat source = new Mat(codec->height, codec->width, MatType.CV_8UC2, (IntPtr)packet.data);
                Cv2.CvtColor(source, dest, ColorConversionCodes.YUV2BGRA_YUYV);

                Invoke(new Action(() =>
                {
                    pboxView.Image = BitmapConverter.ToBitmap(dest); 
                    pboxView.Update();
                }));

                ffmpeg.av_packet_unref(&packet);
            }

            ffmpeg.avformat_close_input(&camera);
        }

        public unsafe void Only_FFmpeg(CancellationToken token)
        {
            AVFormatContext* camera;
            AVPacket packet;

            ffmpeg.avdevice_register_all();
            ffmpeg.avformat_open_input(&camera, "video=YOUR-WEBCAM", ffmpeg.av_find_input_format("dshow"), null);
            ffmpeg.av_init_packet(&packet);

            var codec = camera->streams[0]->codec;
            var dataSize = ffmpeg.av_image_get_buffer_size(AVPixelFormat.AV_PIX_FMT_BGR24, codec->width, codec->height, 1);
            var dataBGR = ffmpeg.av_malloc((ulong)dataSize);
            var sws = ffmpeg.sws_getContext(codec->width, codec->height, codec->pix_fmt, codec->width, codec->height, AVPixelFormat.AV_PIX_FMT_BGR24, ffmpeg.SWS_FAST_BILINEAR, null, null, null);

            while (token.IsCancellationRequested == false && ffmpeg.av_read_frame(camera, &packet) >= 0)
            {
                var linesize = ffmpeg.av_image_get_linesize(codec->pix_fmt, codec->width, 0);
                var linesizeBGR = ffmpeg.av_image_get_linesize(AVPixelFormat.AV_PIX_FMT_BGR24, codec->width, 0);
                ffmpeg.sws_scale(sws, new[] { packet.data }, new[] { linesize }, 0, codec->height, new[] { (byte*)dataBGR }, new[] { linesizeBGR });

                Invoke(new Action(() =>
                {
                    pboxView.Image = new Bitmap(codec->width, codec->height, linesizeBGR, PixelFormat.Format24bppRgb, (IntPtr)dataBGR);
                    pboxView.Update();
                }));

                ffmpeg.av_packet_unref(&packet);
            }

            ffmpeg.av_free(dataBGR);
            ffmpeg.sws_freeContext(sws);
            ffmpeg.avformat_close_input(&camera);
        }

        private void btnRun_Click(object sender, EventArgs e)
        {
            _cancel = new CancellationTokenSource();
            var token = _cancel.Token;

            Task.Run(() => FFmpeg_OpenCV(token), token);
            //Task.Run(() => Only_FFmpeg(token), token);
        }

        private void btnStop_Click(object sender, EventArgs e)
        {
            _cancel.Cancel();
        }
    }
}

FFmpeg.AutoGen @Ruslan-B
OpenCvSharp @shimat

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[WinForm(unsafe)] USBカメラで生データを取得する

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

using FFmpeg.AutoGen;

using OpenCvSharp;
using OpenCvSharp.Extensions;

namespace FFmpegAutoGenApp
{
    public partial class FormMain : Form
    {
        private CancellationTokenSource _cancel;

        public FormMain()
        {
            InitializeComponent();
        }

        public unsafe void FFmpeg_OpenCV(CancellationToken token)
        {
            AVFormatContext *camera;
            AVPacket packet;

            ffmpeg.avdevice_register_all();
            ffmpeg.avformat_open_input(&camera, "video=YOUR-WEBCAM", ffmpeg.av_find_input_format("dshow"), null);
            ffmpeg.av_init_packet(&packet);

            var codec = camera->streams[0]->codec;
            Mat dest = new Mat(codec->height, codec->width, MatType.CV_8UC3);

            while (token.IsCancellationRequested == false && ffmpeg.av_read_frame(camera, &packet) >= 0)
            {
                Mat source = new Mat(codec->height, codec->width, MatType.CV_8UC2, (IntPtr)packet.data);
                Cv2.CvtColor(source, dest, ColorConversionCodes.YUV2BGRA_YUYV);

                Invoke(new Action(() =>
                {
                    pboxView.Image = BitmapConverter.ToBitmap(dest); 
                    pboxView.Update();
                }));

                ffmpeg.av_packet_unref(&packet);
            }

            ffmpeg.avformat_close_input(&camera);
        }

        public unsafe void Only_FFmpeg(CancellationToken token)
        {
            AVFormatContext* camera;
            AVPacket packet;

            ffmpeg.avdevice_register_all();
            ffmpeg.avformat_open_input(&camera, "video=YOUR-WEBCAM", ffmpeg.av_find_input_format("dshow"), null);
            ffmpeg.av_init_packet(&packet);

            var codec = camera->streams[0]->codec;
            var dataSize = ffmpeg.av_image_get_buffer_size(AVPixelFormat.AV_PIX_FMT_BGR24, codec->width, codec->height, 1);
            var dataBGR = ffmpeg.av_malloc((ulong)dataSize);
            var sws = ffmpeg.sws_getContext(codec->width, codec->height, codec->pix_fmt, codec->width, codec->height, AVPixelFormat.AV_PIX_FMT_BGR24, ffmpeg.SWS_FAST_BILINEAR, null, null, null);

            while (token.IsCancellationRequested == false && ffmpeg.av_read_frame(camera, &packet) >= 0)
            {
                var linesize = ffmpeg.av_image_get_linesize(codec->pix_fmt, codec->width, 0);
                var linesizeBGR = ffmpeg.av_image_get_linesize(AVPixelFormat.AV_PIX_FMT_BGR24, codec->width, 0);
                ffmpeg.sws_scale(sws, new[] { packet.data }, new[] { linesize }, 0, codec->height, new[] { (byte*)dataBGR }, new[] { linesizeBGR });

                Invoke(new Action(() =>
                {
                    pboxView.Image = new Bitmap(codec->width, codec->height, linesizeBGR, PixelFormat.Format24bppRgb, (IntPtr)dataBGR);
                    pboxView.Update();
                }));

                ffmpeg.av_packet_unref(&packet);
            }

            ffmpeg.av_free(dataBGR);
            ffmpeg.sws_freeContext(sws);
            ffmpeg.avformat_close_input(&camera);
        }

        private void btnRun_Click(object sender, EventArgs e)
        {
            _cancel = new CancellationTokenSource();
            var token = _cancel.Token;

            Task.Run(() => FFmpeg_OpenCV(token), token);
            //Task.Run(() => Only_FFmpeg(token), token);
        }

        private void btnStop_Click(object sender, EventArgs e)
        {
            _cancel.Cancel();
        }
    }
}

FFmpeg.AutoGen @Ruslan-B
OpenCvSharp @shimat

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C#:SeleniumでWeb解析

執筆のきっかけ・他

・WebスクレイピングをするためにSeleniumを使ったが色々ハマった+C#は情報が少ない
・環境はWin10, VS2017, Google Chrome 83.0.4103.116 (Firefoxでもほぼ変わらず書ける)
・初投稿のため、お手柔らかにご指摘いただけましたら幸いです

目次

前提
ライブラリ
<Chromeドライバの操作>
Chromeを起動する
Webページを開く
新しくタブを開く
タブを閉じる
タブを切り替える
タイムアウト時間を設定する
<要素の取得>
クラス名から要素を取得する
クラス名から要素を取得する(クラス名にスペースが有る)
他の要素を取得する方法
すべての要素を取得する
<要素の解析>
取得した要素のタグで囲われたテキストを取得
取得した要素のクラス名を取得する
取得した要素のHTMLを取得する
取得した要素のTag名を取得する
<他>
その他・ハマったこと
①ウィンドウに対するキー操作が送れない!(Ctrl+sを送りたい)
②上記コードをタイマーで定期的に実行したいけど動かない

前提

ChromeDriverのインスタンス名はchromeとした
文中のHTMLはぼかして書いているので、実際の挙動と齟齬がある可能性があるがご容赦を

ライブラリ

using OpenQA.Selenium.Chrome;

Chromeを起動する

実行するとコマンドプロンプトが立ち上がり、空っぽのウィンドウが立ち上がる

cs
ChromeDriver chrome = new ChromeDriver();

Chromeを起動する.png

Webページを開く

cs
chrome.Url = "https://www.google.com/";

2.Webページを開く.png

新しくタブを開く

他にもページを印刷したり色々できるらしい

cs
chrome.ExecuteScript("window.open()");

タブを閉じる

cs
chrome.Close();

タブを切り替える

例では末尾のタブに切り替えている

cs
chrome.SwitchTo().Window(driver.WindowHandles[GetTabIdxMax()]);
int GetTabIdxMax() { return driver.WindowHandles.Count - 1; }

タイムアウト時間を設定する

webページが開けいない場合のタイムアウト時間を設定

cs
chrome.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(20);

クラス名から要素を取得する

・FindElementsByClassNameを使う
・FindElement"s"にするとぶら下がっている要素すべてをListで受け取れる
・FindElementsで見つからない場合は要素が0個のListが生成される
・FindElementにすると、最初に見つかった要素を拾えて、要素が見つからない場合例外を吐く

例)クラス名List_itemの要素を取得
・itemListにList_itemの要素2つが追加される
・Tag名"li"の要素を拾いたい場合はFindElementsByTagNameを使う

HTML
<ul class="SearchResultList-box">
<li class="List_item">Text1</li>
<li class="List_item">Text2</li>
</ul>
cs
ReadOnlyCollection<IWebElement> itemList= chrome.FindElementsByClassName("List_item");

クラス名から要素を取得する(クラス名にスペースが有る)

スペースが有るとダメなことに気づくまでかなりハマった

HTML
<ul class="SearchResultList-box">
<li class="List item">Text1</li>
<li class="List item">Text2</li>
</ul>
cs
ReadOnlyCollection<IWebElement> itemList= chrome.FindElementsByCssSelector("[class='List item']");

他の要素を取得する方法

ライブラリのメタデータ"RemoteWebDriver"から引用
FindElementsも同じものが用意されている
FindElementとFindElementByCssSelectorについては後述する

cs
        public IWebElement FindElement(By by);
        public IWebElement FindElementByClassName(string className);
        public IWebElement FindElementByCssSelector(string cssSelector);
        public IWebElement FindElementByLinkText(string linkText);
        public IWebElement FindElementByName(string name);
        public IWebElement FindElementByPartialLinkText(string partialLinkText);
        public IWebElement FindElementByTagName(string tagName);
        public IWebElement FindElementByXPath(string xpath);

”FindElementByClassNameで取得した要素”の下にある要素を取得する

FindElementByClassNameで取得した要素に対して、さらにFindElementByClassNameで要素を取得することができない
これにはFindElement/FindElementsを使う。

HTML
<ul class="SearchResultList-box">
<li class="List_item">Text1</li>
<li class="List_item">Text2</li>
</ul>
cs
//クラス名SearchResultList-boxの要素を取得
ReadOnlyCollection<IWebElement> itemList_sb = chrome.FindElementsByClassName("SearchResultList-box");
//クラス名SearchResultList-boxの下にある
//クラス名List_itemの要素を取得
//ここでSearchResultList-boxを検索した場合は、自身は含まれないので要素ゼロのListが生成される
ReadOnlyCollection<IWebElement> itemList_li = itemList_sb[0].FindElements(By.ClassName("List_item"));

すべての要素を取得する

すべての要素を取得したい場合、ワイルドカードが使える
例ではli,liの2要素が取得できる

HTML
<ul class="SearchResultList-box">
<li class="List_item">Text1</li>
<li class="List_item">Text2</li>
</ul>
cs
//クラス名SearchResultList-boxの要素を取得
ReadOnlyCollection<IWebElement> itemList_sb = chrome.FindElementsByClassName("SearchResultList-box");
//クラス名SearchResultList-boxの下にあるすべての要素を取得
ReadOnlyCollection<IWebElement> itemList_wc = itemList_sb[0].FindElements(By.CssSelector("*"));

取得した要素のタグで囲われたテキストを取得

例の"Text1"を拾いたい場合についてFindElementsで取得した要素のTextプロパティより取得できる

HTML
<ul class="SearchResultList-box">
<li class="List_item">Text1</li>
<li class="List_item">Text2</li>
</ul>
cs
//クラス名List_itemの要素を取得
ReadOnlyCollection<IWebElement> itemList = chrome.FindElementsByClassName("List_item");
//List_item1個めのテキスト"Text1"を取得
string text = itemList[0].Text;

取得した要素のクラス名を取得する

例の"List_item"というクラス名を拾いたい場合、FindElementsで取得した要素に対してGetAttributeを使って取得できる

HTML
<ul class="SearchResultList-box">
<li class="List_item">Text1</li>
<li class="List_item">Text2</li>
</ul>
cs
//Tag名"li"の要素を取得
ReadOnlyCollection<IWebElement> itemList = chrome.FindElementsByTagName("li");
//Tag名liの1個めのクラス名"List_item"を取得
string text = itemList[0].GetAttribute("class");

取得した要素のHTMLを取得する

例では、textの中身が
<li class="List_item">Text1</li>
<li class="List_item">Text2</li>

になる。
改行コードがLFの場合がほとんどなので、メモ帳に貼り付けると1行に。。。

HTML
<ul class="SearchResultList-box">
<li class="List_item">Text1</li>
<li class="List_item">Text2</li>
</ul>
cs
//Tag名"ul"の要素を取得
ReadOnlyCollection<IWebElement> itemList = chrome.FindElementsByTagName("ul");
//HTMLを取得
string text = itemList[0].GetAttribute("innerHTML");

取得した要素のTag名を取得する

例の"ul"というTag名を拾いたい場合、FindElementsで取得した要素のTagNameプロパティが使える
クラス名がGetAttribute("class");で拾えるのでTagNameとすれば拾えるかと思いきやダメだった

HTML
<ul class="SearchResultList-box">
<li class="List_item">Text1</li>
<li class="List_item">Text2</li>
</ul>
cs
//class名SearchResultList-boxの要素を取得
ReadOnlyCollection<IWebElement> itemList = chrome.FindElementsByClassName("SearchResultList-box");//TagName
//Tag名uiを取得
string text = itemList[0].TagName;

以上で解説終わり!
これで簡単なWebページ解析ができるようになるはずです!

その他・ハマったこと

①ウィンドウに対するキー操作が送れない!(Ctrl+sを送りたい)

 Chromeでページを完全ページとして保存したかったため、Ctrl+sキーを送信して
 Webページを保存しようとした。htmlだけの保存だと中途半端なので。。。
 がしかし、ウィンドウに対するキー操作は受け付けてくれない模様。
 ※Ctrl+aで全選択はできるのにWhy!? もちろんFirefoxもダメ!
例)
 chrome.FindElementByTagName("html").SendKeys(Keys.LeftControl + "s" );

 継ぎ接ぎだらけではあるが、下記を参考に保存できたのでメモしておく
 要約すると、Firefoxのプロセスを探して強制的にActiveにして、外部からCtrl+sを送っている
 <参考URL>
  感謝!
  https://qiita.com/yaju/items/af308376f04ef2ff1325

 ※他にwebブラウザを開いているとそっちを拾ってしまうので全て閉じておいてください
button5_Clickは適当に作ったボタン

cs
        [DllImport("User32.dll")]
        static extern int SetForegroundWindow(IntPtr point);
        [DllImport("user32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int x, int y, int cx, int cy, int uFlags);

        // ウィンドウをアクティブにする
        public static void SetActiveWindow(IntPtr hWnd)
        {
            const int SWP_NOSIZE = 0x0001;
            const int SWP_NOMOVE = 0x0002;
            const int SWP_SHOWWINDOW = 0x0040;

            const int HWND_TOPMOST = -1;
            const int HWND_NOTOPMOST = -2;

            SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
            SetWindowPos(hWnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE);
        }

        [DllImport("user32.dll")]
        private static extern bool IsIconic(IntPtr hWnd);
        [DllImport("user32.dll")]
        private static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
        [DllImport("user32.dll")]
        private static extern int GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
        [DllImport("user32.dll")]
        private static extern IntPtr GetForegroundWindow();
        [DllImport("user32.dll")]
        private static extern bool AttachThreadInput(int idAttach, int idAttachTo, bool fAttach);

        [DllImport("user32.dll")]
        public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
        [DllImport("user32.dll")]
        public static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
        [DllImport("user32.dll")]
        public static extern IntPtr PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

        [DllImport("user32.dll", EntryPoint = "FindWindowEx")]
        public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);

        // ウィンドウを強制的にアクティブにする
        public static void ForceActive(IntPtr handle)
        {
            const int SW_RESTORE = 9;

            // 最小化状態なら元に戻す
            if (IsIconic(handle))
                ShowWindowAsync(handle, SW_RESTORE);

            int processId;
            // フォアグラウンドウィンドウを作成したスレッドのIDを取得         
            int foregroundID = GetWindowThreadProcessId(GetForegroundWindow(), out processId);
            // 目的のウィンドウを作成したスレッドのIDを取得
            int targetID = GetWindowThreadProcessId(handle, out processId);

            // スレッドのインプット状態を結び付ける   
            AttachThreadInput(targetID, foregroundID, true);
            // ウィンドウをフォアグラウンドに持ってくる
            SetForegroundWindow(handle);
            // スレッドのインプット状態を切り離す
            AttachThreadInput(targetID, foregroundID, false);
        }

        private void button5_Click(object sender, EventArgs e)
        {
            SaveFireFox();
        }

        public void SaveFireFox()
        {
            System.Diagnostics.Process pFireFox = null;
            foreach (System.Diagnostics.Process p in System.Diagnostics.Process.GetProcesses())
            {
                //メインウィンドウのタイトルがある時だけ列挙する
                if (p.MainWindowTitle.Length != 0)
                {
                    if (p.ProcessName == "firefox")
                    {
                        pFireFox = p;
                    }
                 }
            }
            if (pFireFox != null)
            {
                //FireFoxをアクティブに
                ForceActive(pFireFox.MainWindowHandle);
                //Ctrl+sを送る→保存画面が出る
                SendKeys.SendWait("^s");
                Thread.Sleep(3000);
                //Enterを送って保存
                SendKeys.SendWait("{Enter}");
                Thread.Sleep(3000);
            }
        }

②上記コードをタイマーで定期的に実行したいけど動かない

Task実行しないとウィンドウが固まってしまうため、timerで定期的に実行するようにしたら、なぜか動かない。未解決。
DoEventsを途中で挟むとそれ以降保存できなくなるのでそのあたりになにかあると思われる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Outlookのメール作成・取得をC#でやってみる - Visual Studio不使用 - 自分用メモ

ひな形ではやれないような、柔軟なテンプレメールの作成とか、
使いづらいメール検索機能とかを何とかしたい。(「高度な検索」使うのめんどい)
というわけで手掛かりレベルのものを残してみた。。

詳細は参考サイト参照ください・・

コンパイルバッチ

OutlookのバージョンやWindowsのバージョンによっては微妙に違うフォルダにいるかもです。

compile.bat
csc ^
 /r:C:\Windows\assembly\GAC_MSIL\Microsoft.Office.Interop.Outlook\15.0.0.0__71e9bce111e9429c\Microsoft.Office.Interop.Outlook.dll ^
 /r:C:\Windows\assembly\GAC_MSIL\office\15.0.0.0__71e9bce111e9429c\Office.dll ^
 %*

コンパイル方法

csc.exeのパスは事前に通しておく必要あり。
自分の環境では下記にいました。
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe

compile.bat OutlookMailFinder.cs

ソース

Outlook起動済みで使う前提で作っています。
new Outlook.Application()を使えば起動させることも可能。

OutlookMailFinder.cs
using System;
using System.Drawing;
//using System.Collections.Generic;
//using System.Reflection;
using System.Runtime.CompilerServices; // to use [MethodImpl(MethodImplOptions.NoInlining)]
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Windows.Forms;

using Outlook = Microsoft.Office.Interop.Outlook;


class OutlookMailFinder : Form
{
    TextBox txtSenderMailAddress;
    Button btnStartTryGet;


    [MethodImpl(MethodImplOptions.NoInlining)]
    void LaunchSendMail()
    {
        Outlook.Application oOutlookApp = null;
        try {
            oOutlookApp = (Outlook.Application)Marshal.GetActiveObject("Outlook.Application");
        }
        catch ( COMException ) { // COMException封殺するのはあまりお勧めしない・・
            Console.WriteLine("Cannot found active Outlook application");
            //oOutlookApp = new Outlook.Application();
            return;
        }

        var mailItem = (Outlook.MailItem)oOutlookApp.CreateItem(Outlook.OlItemType.olMailItem);
        if (mailItem != null)
        {                
            Outlook.Recipient to = mailItem.Recipients.Add("XXX@XXX.co.jp");
            to.Type = (int)Outlook.OlMailRecipientType.olTo;

            Outlook.Recipient cc;
            cc = mailItem.Recipients.Add("YYY@YYY.co.jp");
            cc.Type = (int)Outlook.OlMailRecipientType.olCC;

            cc = mailItem.Recipients.Add("ZZZ@ZZZ.co.jp");
            cc.Type = (int)Outlook.OlMailRecipientType.olCC;

            // アドレス帳の表示名で表示できる
            mailItem.Recipients.ResolveAll();

            mailItem.Subject = "件名";
            mailItem.Body = "本文";
    
            // 表示(Displayメソッド引数のtrue/falseでモーダル/モードレスウィンドウを指定して表示できる)
            mailItem.Display(false);
        }
    }


    [MethodImpl(MethodImplOptions.NoInlining)]
    void GetMails()
    {
        Outlook.Application oOutlookApp = null;
        try {
            oOutlookApp = (Outlook.Application)Marshal.GetActiveObject("Outlook.Application");
        }
        catch ( COMException ) { // COMException封殺するのはあまりお勧めしない・・
            Console.WriteLine("Cannot found active Outlook application");
            //oOutlookApp = new Outlook.Application();
            return;
        }

        var folder = oOutlookApp.Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox);
//      var mapiFolders = oOutlookApp.Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox).Folders;

//      foreach(Outlook.Folder folder in mapiFolders)
        {
            Console.WriteLine(folder.Name);


            foreach(Outlook.MailItem mail in folder.Items)
            {
                //ヘッダー・送信者アドレス・題名・本文を取得
                //Console.WriteLine(mail.PropertyAccessor.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x007D001E"));
                Console.WriteLine(mail.SenderEmailAddress);
                Console.WriteLine(mail.Subject);
                // Console.WriteLine(mail.Body);

                GC.Collect();
                GC.WaitForPendingFinalizers();
                // GC入れないと下記で落ちる場合がある
                //System.Runtime.InteropServices.COMException (0x9077000E): メモリまたはシステム リソースが不足しています。いくつかのウィンドウまたはプログラムを終了してから、再度実行してください。
                //   場所 Microsoft.Office.Interop.Outlook._MailItem.get_SenderEmailAddress()
            }
        }
    }

    OutlookMailFinder()
    {

        Controls.Add(txtSenderMailAddress = new TextBox(){
            Location = new Point(0,0),
            Width = 150
        });


        Controls.Add(btnStartTryGet = new Button(){
            Text = "Get",
            Location = new Point(180, 0),
            Width = 100
        });
        btnStartTryGet.Click += (s,e)=>{
            // GetMails();
            LaunchSendMail();
        };


        Load      += (s,e)=>{MyResize();};
        Resize    += (s,e)=>{MyResize();};
        ResizeEnd += (s,e)=>{MyResize();};

    }

    void MyResize()
    {
        /*
        int w = ClientSize.Width;
        int h = ClientSize.Height;// - txtContent.Top;
        if(w<50){w=50;}
        if(h<50){h=50;}
        txtContent.Size = new Size(w,h);
        */
    }

    [STAThread]
    static void Main(string[] args)
    {
        Application.Run(new OutlookMailFinder());
    }
}

参考サイト

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む