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

[C#]GeoTag(GPS情報)をjpgファイルに設定/取得するときのハマったメモ

もくじ
https://qiita.com/tera1707/items/4fda73d86eded283ec4f

GeoTag関連
- GeoTag(GPS情報)をjpgファイルに設定/取得する
- GeoTag(GPS情報)をjpgファイルに設定/取得するときのハマったメモ

やりたいこと

UWPのAPI(GeotagHelper.SetGeotagAsync())を使って、jpegファイルにGeoTagを付与したいのだが、あるjpegファイルだけ、なぜかGeotagHelper.SetGeotagAsync()を実行した際に例外が発生してしまう。
image.png

jpegファイルをちゃんとSetGeotagAsyncにセットしてやっているはずが、なんで例外になるのかさっぱりわからない。そのときに調べた(というかいろいろ試した)事のメモ。

結論(例外の原因)

原因は、GeoTag付与しようとしていたjpegファイルが、実はjpegファイルではなかったこと。
具体的には、そのjpegファイルをSystem.Drawing.BitmapSave()メソッドを使って作成していたが、Saveの引数にSystem.Drawing.Imaging.ImageFormat.Jpegを指定してJpeg作成すべきが、System.Drawing.Imaging.ImageFormat.Bmpを指定して、ファイルの拡張子だけ.jpegにしてjpegファイルにしていたことが原因。

Windowsでは、拡張子が.bmpのビットマップファイルの拡張子だけを.jpegに変えてやると普通にjpegとしてペイントやその他アプリで開けるようになるが、GeotagHelper.SetGeotagAsync()的にはそれはダメで、拡張子だけではなく生粋の.jpgファイルでないと、ためした限り受け付けてくれないらしい。
(EXIFのデータを乗っける領域がbmpファイルの先頭部分には用意されてないからかも。)

サンプル

var cur = Directory.GetCurrentDirectory();      // exeのあるディレクトリ
var filepath = cur + @"\ginga.bmp";             // 元の画像
var filepath_out_jpg = cur + @"\ginga_out.jpg"; // jpgとして保存する画像
var filepath_out_bmp = cur + @"\ginga_out_fake.jpg"; // 実はbmpだけどjpgとして保存する画像

using (var fs = new FileStream(filepath, FileMode.Open, FileAccess.ReadWrite))
using (var bmp = new System.Drawing.Bitmap(fs))
{
    // 元の画像を、jpgとbmpで保存し分ける
    bmp.Save(filepath_out_jpg, System.Drawing.Imaging.ImageFormat.Jpeg);
    bmp.Save(filepath_out_bmp, System.Drawing.Imaging.ImageFormat.Bmp);
}

// GPS値作成
BasicGeoposition bgps = new BasicGeoposition() { Latitude = 3.0, Longitude = 2.0, Altitude = 1.0 };
// GPS値をGeopointにセット
Geopoint gps = new Geopoint(bgps);

try
{
    // GPS値をjpgファイルに書き込み
    var stjpg = await Windows.Storage.StorageFile.GetFileFromPathAsync(filepath_out_jpg);
    await GeotagHelper.SetGeotagAsync(stjpg, gps);// →こっちは問題なくgeotag付与できる
    var stbmp = await Windows.Storage.StorageFile.GetFileFromPathAsync(filepath_out_bmp);
    await GeotagHelper.SetGeotagAsync(stbmp, gps);// →こっちは、拡張子はjpgだが実はbmpでjpgではないので、geotag付与時に例外発生
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

反変性インタフェースと共変性インタフェース in C#

はじめに

共変性,反変性インタフェースという言葉を知っていても知らなくても,このルールはよくよく考えればあたり前の話で,言葉を知らなくてもほとんどの人はその概念を理解しているのではないでしょうか.しかし職場の先輩などに言われて,言葉の意味を知っているのに言葉自体を知らずに悔しい思いをしないように,なるべく短くメモを残しておきました.

準備1(暗黙的代入)

共変性,反変性は「ある型をもつ変数へ別の型をもつインタフェースを実装したオブジェクトが代入できるかどうかの性質」なのですが,実際にはインタフェースを実装したオブジェクトが変数に代入される場面で問題になるのではなく,メソッド呼び出し時の引数としての暗黙的代入でエラーになったりならなかったりします.
int func(int a);
というシグネチャを持つメソッドを呼び出すときに
func(5);
とすると暗黙的に
int a=5と同じことになります.これを,ここでは暗黙的代入とします.代入をメソッドへの引数の引き渡しと同じこととみなして,
本稿では共変性,反変性は「ある型の引数をとるメソッドに別の型をもつインタフェースを実装したオブジェクトが引数として引き渡せるかどうかの性質」として,C#のIComparerとIEnumeratorで実験してみます.

準備2 親子クラス

AnimalとDogの2つのクラスが親子関係になっています.DogオブジェクトはAnimal変数に代入できますが,AnimalオブジェクトはDog変数に代入できません.

class Animal
    {
        public int id { get; set; }
        public int weight { get; set; }
    }
class Dog : Animal
    {
        public int speed { set; get; }
    }
Animal a =new Dog();//可能(アップキャスト)
//Dog d = new Animal();//コンパイルエラー(ダウンキャスト)

ここで,以下の実験のために2つの配列を用意しておきます.初期化子パラメータは適当です.

Dog[] ad = {
                new Dog(){id=1,weight=1,speed=10},
                new Dog(){id=2,weight=2,speed=9},
                new Dog(){id=3,weight=3,speed=8}
            };
Animal[] aa = {
                new Animal(){id=10,weight=3},
                new Animal(){id=20,weight=1},
                new Animal(){id=30,weight=2}
            };

反変性インタフェースの例 IComparer<T>

前で用意した配列をソートするためIComparer<T>を実装したクラスを作成します.

class Dcompare : IComparer<Dog>
{
        public int Compare(Dog x, Dog y)
        {
            return x.speed - y.speed;
        }
}
class Acompare : IComparer<Animal>
{
        public int Compare(Animal x, Animal y)
        {
            return x.weight - y.weight;
        }
}

この2つの比較クラスで,それぞれ親子クラスをたすき掛けでソートしてみるとどうなるでしょうか.

Array.Sort<Dog>(ad, new Dcompare());//speedでソート
Array.Sort<Dog>(ad, new Acompare());//weightでソート
//Array.Sort<Animal>(aa, new Dcompare());//compile error

本来Dog配列をソートするのにはDcompare()を使って,speedプロパティによって並べ替えられます(1行目).しかし,上記例のとおりDcompare()が入るべきIComparer<Dog>型引数位置にIComparer<Animal>型を実装したオブジェクトを引き渡してweightプロパティで並び替えることもできます(2行目).一方IComparer<Animal>型が来るべき位置にIComparer<Dog>型を引き渡せません(3行目).2行目と3行目を暗黙的代入に書き換えると,それぞれ以下のようになります.

IComparer<Dog> x =  new Acompare();//可能(ダウンキャスト風)
IComparer<Animal> y =  new Dcompare();//コンパイルエラー(アップキャスト風)

これはAnimal型変数へDog型オブジェクトの代入が許されたアップキャストの逆の関係にみえます.この性質からIComparer<T>は反変性インタフェースであると言われます.

共変性インタフェースの例 IEnumerable<T>

共変性の性質をもつインタフェースの例としてIEnumerable<T>があります.class Dogs: IEnumerable<Dog>とclass Animals : IEnumerable<Animal>の2種類のクラスがあるものとします.この2つの変数型とオブジェクト型を入れ替えて代入してみると,一方がエラーになります.

IEnumerable<Animal> ae = new Dogs();
//IEnumerable<Dog> de = new Animals();//compile error

これはAnimal型変数へDog型オブジェクトの代入が許されたアップキャストと同じ関係にみえます.この性質からIEnumerable<T>は共変性インタフェースであると言われます.
一般的にIEnumerable<T>はメソッドに代入されるのではなく,foreach 文のinの後に置かれます.しかし,foreachでの型不一致はコンパイルエラーではなくランタイム時のキャスト例外になります.この場合でもIEnumerable<Dog>を置くべきところにIEnumerable<Animal>を置けないという共変性ルールは同じです.

foreach (Animal a in new Dogs())
{
    Console.WriteLine(a.ToString());
}
foreach (Dog d in new Animals())//不正キャスト例外
{
    Console.WriteLine(d.ToString());
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ASP.NET Core Blazor WebAssembly のハローワールド

Blazor WebAssembly を試してみようかなと思い立ったので以下のドキュメントを写経してみました。

ASP.NET Core Blazor の概要

プロジェクトテンプレートのインストール

今のところプレビューなので自前で入れます。.NET Core SDk 自体も 3.1.102 以降である必要があります。私は、現時点での最新版の 3.1.201 が入っていました。以下のコマンドでプロジェクトテンプレートをいれます。

dotnet new -i Microsoft.AspNetCore.Components.WebAssembly.Templates::3.2.0-preview2.20160.5

入れると、Visual Studio のプロジェクトテンプレートの Blazor アプリの中に Blazor WebAssembly App が生えます。

image.png

右下の ASP.NET Core hosted にチェックを入れると、ASP.NET Core のプロジェクトも作れて、そこに WebAssembly も入れてデプロイ出来るので Azure WebApps とかにデプロイするのが楽そうなので、それのチェックを入れて作ります。

image.png

新規作成すると、以下のようなテンプレートが生成されます。
この段階で ASP.NET Core 側の API を叩いて画面に表示する例のコードまで入ってい
るのはありがたいですね。Server 側プロジェクトにある WetherForecastController が WebAPI です。

image.png

Client 側プロジェクトの Pages/FetchData.razor を見ると以下のように WetherForecast の URL 叩いています。いいね。

image.png

ローカル実行

何も考えずにローカル実行をするとブラウザーが立ち上がって WebAssembly の Blazor が動きますね。完璧。

image.png

Azure にデプロイ

Azure WebApps にデプロイしてみましょう。デプロイするのは Server 側のプロジェクトです。右クリックから発行を選びます。

image.png

適当に発行先を選んで(もしくは新規作成)デプロイをすると、本当にすんなりと動きます。

image.png

WebApp とかだと https://サイト名.azurewebistes.net/ 直下に作られるので気にしなくていいのですが、そうではなくて https://example.com/YourAppName/ のようなパスの下に展開されるときはアプリのベースパスの設定が必要なので、デプロイするときは、そこに気を付けましょう。

アプリのベース パス

起動シーケンスを少し見てみる

プレビューなのでデプロイまで、もう少しハマると思ったら、何もハマらなかったのでちょっと拍子抜けしました。少し WebAssembly 上での起動時のシーケンスでも追ってみようと思います。

Client 側プロジェクトには Program.cs があります。ここにある Main メソッドがクライアントサイドの C# としてのエントリーポイントになるでしょう。

見てみると以下のような感じで WebAssmeblyHostBuilder を作ってルートのコンポーネントの登録をしたり、Http 呼び出しに使う HttpClient クラスの登録をしてから実行してるように見えます。

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Text;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.DependencyInjection;

namespace HelloBlazor.Client
{
    public class Program
    {
        public static async Task Main(string[] args)
        {
            var builder = WebAssemblyHostBuilder.CreateDefault(args);
            builder.RootComponents.Add<App>("app");

            builder.Services.AddBaseAddressHttpClient();

            await builder.Build().RunAsync();
        }
    }
}

App クラスはどうなっているかというと、ただの App.razor ファイルです。

<Router AppAssembly="@typeof(Program).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
    </Found>
    <NotFound>
        <LayoutView Layout="@typeof(MainLayout)">
            <p>Sorry, there's nothing at this address.</p>
        </LayoutView>
    </NotFound>
</Router>

<Found Context="routeData"> の部分と <RouteView RouteData="@routeData" の routeData は、名前が一致していないといけないようですね。DefaultLayout は typeof(MainLayout) となっているので、Shared/MainLayout.razor がレイアウト定義に使われています。

@inherits LayoutComponentBase

<div class="sidebar">
    <NavMenu />
</div>

<div class="main">
    <div class="top-row px-4">
        <a href="http://blazor.net" target="_blank" class="ml-md-auto">About</a>
    </div>

    <div class="content px-4">
        @Body
    </div>
</div>

@Body でレイアウトファイルを適用したファイルの中身が展開される場所なんでしょうね。

App.razor の Router タグは自動的に RouteAttribute が適用されているもの(.razor ファイル内で @page が指定されているもの)を探してくれます。

例えば Pages/Index.razor を見ると @page "/" と書かれています。

@page "/"

<h1>Hello, world!</h1>

Welcome to your new app.

<SurveyPrompt Title="How is Blazor working for you?" />

これで、デフォルトの https://example.com/ などのような URL にアクセスされたときに自動的に Index.razor に行きつくようになっています。

Program.cs の builder の Services に自前クラスを追加することで DI も動きますね。

例えばこんな感じ MyClass を追加して…

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Text;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.DependencyInjection;

namespace HelloBlazor.Client
{
    public class Program
    {
        public static async Task Main(string[] args)
        {
            var builder = WebAssemblyHostBuilder.CreateDefault(args);
            builder.RootComponents.Add<App>("app");

            builder.Services.AddBaseAddressHttpClient();
            builder.Services.AddSingleton<MyClass>();

            await builder.Build().RunAsync();
        }
    }

    public class MyClass
    {
        public string Message => "Hello world";
    }
}

Index.razor などで使うには @inject クラス名 変数名 のような行を追加したらインジェクション出来ます。

@page "/"
@inject MyClass myClass

<h1>Hello, world!</h1>

<p>@myClass.Message</p>

Welcome to your new app.

<SurveyPrompt Title="How is Blazor working for you?" />

FetchData.razor のように C# のコードを .razor の中に直接うめることは出来ますが、partial class を使って .cs ファイルに分離を出来ます。

部分クラスのサポート

まとめ

結構いい感じに出来そうですね。HTML/CSS は好きだけど JavaScript が苦手な人にはいいかもしれない。WebAssembly 系の弱点の起動処理が重い点を除けば…。

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

役に立たない定規をつくる

 次の写真に示す定規を作ってみた。何の役にも立たない定規だが、Formアプリケーションに必要な動作を幾つか組み込んである。
定規起動時.png

1.機能

 この定規でできることは、次のとおりである。
(1)定規の目盛りを読むために黄色い矢印をドラッグして鶯色の領域に配置できる。
           黄色い矢印.png
(2)この矢印は複数個配置できる。
複数個配置.png
(3)配置した矢印の上にマウスポインタを乗せると、その矢印が示している指示値を矢印の横に表示し、また目盛り上に赤いカーソルを表示する。
        読み取り値表示2.png
(4)表示された指示値は、通常矢印の右側に表示されるが、矢印が定規の右側の値を指示している時、指示値が見切れるのを防ぐため、矢印の左側に表示される。
(5)配置した矢印をドラッグして、指し示す位置を変えることができる。
(6)配置した矢印をマウスで選択して、矢印キー(←、→)で指し示す位置を変えることができる。
(7)少しずれて重なった2つの矢印の内、下側の矢印にマウスを重ねると前面に表示される。
    重なった時1.png     重なった時3.png

(8)完全に重なった矢印をダブルクリックすると、一つ下に重なった矢印が前面に現れる。
(9)矢印の上にマウスポインタを置き、左クリックするとメニューが開き、削除の項目が表示され、これを選択するとその矢印が削除できる。
        コンテキストメニュ削除.png
(10)定規の目盛りを拡大縮小できる。
拡大.png

2.機能を実現するために

 組み込まれた機能の内、いくつかの機能についてコードを用いて説明する。

2.1 矢印の機能を盛り込んだArrowクラス

 この定規を実現するために、まずArrowクラス(矢印クラス)を作った。Arrowクラスは、PictureBoxクラスを継承し、更に矢印が指し示す目盛りの値を記憶したおくGraduationプロパティを持っている。

Arrow.cs
using System.Drawing;
using System.Windows.Forms;

namespace Ruler
{
    public class Arrow:PictureBox
    {
        private double graduationValue;
        private const int ArrowWidth = 21;
        private const int ArrowHeight = 44;

        public Arrow()
        {
            this.Image= Properties.Resources.VNoneTag;
            this.Size = new Size(ArrowWidth, ArrowHeight);
        }

        public double Graduation
        {
            set
            {
                this.graduationValue = value;
            }
            get
            {
                return this.graduationValue;
            }
        }
    }
}

また、次に示す矢印のイメージを持っている。
     VNoneTag.png

2.2 フォームの構造

 この定規アプリケーションのメインクラスは、RulerMainクラスである。このクラスは、Formクラスを継承している。RulerMainクラスのフォームは、次のような構造になっている。まず第1層になるフォームの上にpanel1(下の図の鶯色の部分)、baseArrowと呼ばれるPictureBoxを継承したArrowクラスのインスタンス(縮小ボタンの右側の矢印)、拡大・縮小を行うためのbutton1と2の計4つのコントロールが配置されている。これらのコントロールは、RulerMain()コンストラクタで定義されている。
複数個配置.png

        private RulerMain()
        {
                 // (省略)
            this.Controls.Add(this.panel1);
            this.Controls.Add(baseArrow);
            this.Controls.Add(button1);
            this.Controls.Add(button2);
                 // (省略)
       }

 panel1の上には、定規の目盛りのイメージを表示するpictureBox1、定規の目盛りを指示する矢印を配置する鶯色のpanel2が載っている。

        private RulerMain()
        {
                 // (省略)
            this.panel1.Controls.Add(pictureBox1);
                 // (省略)
            this.panel1.Controls.Add(panel2);
                 // (省略)
        }

また、panel2には、定規の目盛りを指し示すArrowクラスのインスタンスarrowが配置される。

        // 矢印を定規の矢印配置エリアにドロップするためのメソッド
        private void baseArrow_MouseUp(Object sender, MouseEventArgs e)
        {
                 // (省略)
                    panel2.Controls.Add(arrow);
                 // (省略)
        }

2.3 黄色い矢印baseArrowをドラッグして鶯色の領域に配置するためのコード

 縮小ボタンの右横に配置されている黄色い矢印、baseArrowをドラッグし、鶯色の領域でドロップするとその領域に配置できる。
          黄色い矢印.png      読み取り値表示2.png

 baseArrowは、つぎのコードのようにイベントハンドラが動作するようにデリゲートが組み込まれている。

        private RulerMain()
        {
                   // (省略)
            // 黄色い矢印の定位置を表示する矢印の定義 
            this.baseArrow = new Arrow();
            this.baseArrow.MouseDown += new MouseEventHandler(baseArrow_MouseDown);
            this.baseArrow.MouseMove += new MouseEventHandler(baseArrow_MouseMove);
            this.baseArrow.MouseUp += new MouseEventHandler(baseArrow_MouseUp);
                   // (省略)
        }

 baseArrowの上で左マウスボタンを押すと、baseArrow_MouseDown()イベントハンドラが起動する。このイベントハンドラの中でフォームの中を自由に移動できるmovingArrowが作られる。movingArrowは、this.Controls.Add(movingArrow)メソッドでフォームのコントロールコレクションに加えられ、またthis.Controls.SetChildIndex(movingArrow, 0)メソッドでフォームの表示の順番が最前面になる。これにより、他のコントロールに隠れなくなる。
 また ConvertDrugedArrowCoordinates(e, baseArrow, this)メソッドで、マウスの位置の座標系がbaseArrowの座標系からフォームの座標系に変換され、movingArrowに与えられる。

        // 黄色のpictureBox2をクリックした時の操作
        private void baseArrow_MouseDown(Object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)  // 左クリックをした時
            {
                isDragPictureBox = true;
                movingArrow = new Arrow();

                this.Controls.Add(movingArrow);
                this.Controls.SetChildIndex(movingArrow, 0);
                this.movingArrow.Image = Properties.Resources.VMoveTag;
                this.movingArrow.Location = ConvertDrugedArrowCoordinates(e, baseArrow, this);
            }
        }

 座標変換を行うConvertDrugedArrowCoordinates(e, baseArrow, this)メソッドは、PointToScreen()メソッドとPointToClient()メソッドで座標変換を行っている。

        // MouseEventArgs eで与えられる点をマウスポインタが矢印をの座標を(ArrowCenterX, 10)の位置でドラッグするようにオフセットし、座標系をマウスのあるc1から、座標系c2に変換し、Point型で返す。
        private Point ConvertDrugedArrowCoordinates(MouseEventArgs e, Control c1, Control c2)
        {
            return ConvertArrowCoordinates(e, c1, c2, ArrowCenterX, 10);
        }

        //
        // MouseEventArgs eで与えられるマウスポインタの座標をオフセット(xOffset, yOffset)だけずらし、座標系をマウスのあるc1から、座標系c2に変換し、Point型で返す。
        private Point ConvertArrowCoordinates(MouseEventArgs e, Control c1, Control c2, int xOffest, int yOffset)
        {
            Point pTemp = new Point();  // マウスポインタの値ーオフセットの値が代入される
            Point pInC1;                // C1(マウス)の座標系
            Point pInC2;                // C2の座標系

            pTemp.X = e.X - xOffest; // ArrowCenterX(=11)は矢印のx軸の原点をずらすため(マウスポインタが矢印のX軸の中心を指すようにするため)
            pTemp.Y = e.Y - yOffset;
            pInC1 = c1.PointToScreen(pTemp);
            pInC2 = c2.PointToClient(pInC1);

            return pInC2;
        }

マウスの移動に伴って private RulerMain()メソッドにあるthis.baseArrow.MouseMove += new MouseEventHandler(baseArrow_MouseMove);の行のデリゲートにより、baseArrow_MouseMoveイベントハンドラが起動し、movingArrowが移動することになる。ここでも、ConvertDrugedArrowCoordinates(e, baseArrow, this);によって変換されたマウスの位置がmovingArrowのLocationプロパティーに与えられる。

        // 初期位置にある矢印をドラッグしながら移動させる為のメソッド
        private void baseArrow_MouseMove(Object sender, MouseEventArgs e)
        {
                       //(省略)
                    // マウスの位置を座標変換した後、矢印の位置を変換する
                    this.movingArrow.Location = ConvertDrugedArrowCoordinates(e, baseArrow, this);
        }

 移動する矢印movingArrowをドラッグし目盛りと鶯色の境界付近でマウスボタンを離すと、private RulerMain()メソッドのthis.baseArrow.MouseUp += new MouseEventHandler(baseArrow_MouseUp);の行のデリゲートにより登録されたbaseArrow_MouseUp(Object sender, MouseEventArgs e)イベントハンドラが起動し、矢印はドロップされる。
 この時、Arrow型のarrowインスタンスが作られ、arrowにいくつかのイベントハンドラが登録され、またpanel2.Controls.Add(arrow);の行で、arrowがpanel2に登録され、ディスプレイ上に表示される。また同時にthis.Controls.Remove(movingArrow);の行で、ディスプレイ上からmovingArrowの表示を消す。

        private void baseArrow_MouseUp(Object sender, MouseEventArgs e)
        {
                           // (省略)
                    // 矢印をドロップしたときに新しい矢印を作り、panel2に配置する
                    Arrow arrow = new Arrow();
                    // 矢印に登録するイベントハンドラ
                    // 矢印にデリゲートによってイベントハンドラを登録する
                    arrow.DoubleClick += arrow_DoubleClick;     // 重なったarrowの順番を変える
                    arrow.MouseEnter += arrow_MouseEnter;       // 矢印が示す指示値を表示する
                    arrow.MouseLeave += arrow_MouseLeave;       // 矢印が示す指示値を隠す
                    arrow.MouseDown += arrow_MouseDown;         // 矢印の位置を変えるためにドラッグする
                    arrow.MouseMove += arrow_MouseMove;         // 矢印の位置を変える
                    arrow.MouseUp += arrow_MouseUp;             // 矢印の位置を定める
                    arrow.KeyDown += arrow_KeyDown;             // 矢印を矢印キーで動かすためのデリゲート
                    arrow.KeyUp += arrow_keyUp;                 // 矢印を動かした矢印キーを離したときのデリゲート
                           // (省略)
                    panel2.Controls.Add(arrow);
                           // (省略)

                // フォームのコントロールからmovedPictueBoxを削除する。
                this.Controls.Remove(movingArrow);

                isDragPictureBox = false;
            }
        }

2.4 配置した矢印の上にマウスポインタを乗せ、指示値を表示するためのコード

 鶯色のpanel2に配置した矢印の上にマウスポインタを置くと、矢印が示している値が表示される。
              読み取り値表示2.png
 これは、baseArrow_MouseUp(Object sender, MouseEventArgs e)メソッドのarrow.MouseEnter += arrow_MouseEnter;の行で登録されたarrow_MouseEnterイベントハンドラが起動したためである。

        private void baseArrow_MouseUp(Object sender, MouseEventArgs e)
        {
                           // (省略)
                    arrow.MouseEnter += arrow_MouseEnter;       // 矢印が示す指示値を表示する
                    arrow.MouseLeave += arrow_MouseLeave;       // 矢印が示す指示値を隠す
                           // (省略)
        }

 arrow_MouseEnter(object sender, EventArgs e)イベントハンドラでは、 showGraduation(object sender)メソッドが呼ばれ、矢印の指示値が表示される。

        private void arrow_MouseEnter(object sender, EventArgs e)
        {
                       // (省略)
            showGraduation(sender);     // 矢印が示している目盛りの値を表示する
                       // (省略)
        }

 showGraduation(object sender)メソッドでは、senderをArrowクラスでダウンキャストすることで、複数あるarrowオブジェクトの内、どのarrowオブジェクトからsenderが送られた来たか特定できる。(注:この記述は、panel2に複数の矢印(arrowオブジェクト)が配置されていることを前提としている。)
 例えば((Arrow)sender).Locationのように記述すれば、マウスが置かれた矢印の位置を知ることがで、また(Arrow)sender).Graduationと記述すれば、マウスが指し示している目盛りの値をしることができる。

        // 矢印の横に、矢印が示している値を表示するメソッド
        private void showGraduation(object sender)
        {
                        //(省略)
            Point pArrow = ((Arrow)sender).Location;
            label1.Text = $"矢印{tagNum}の位置は{((Arrow)sender).Graduation}です。";
                        //(省略)

        }

2.5 少しずれて重なった2つの矢印の内、下側の矢印にマウスを重ねると前面に表示させるためのコード

 下の図のように、下に重なった矢印の上にマウスポインタを重ねると、下側にあった矢印が上側になるようコードを工夫した。
    重なった時1.png     重なった時3.png

MouseEnter(object sender, EventArgs e)イベントハンドラの中で、panel2.Controls.SetChildIndex((Arrow)sender, 0); メソッドを用い、panel2で表示されている矢印arrowの順番を最前面に変えている。

        private void arrow_MouseEnter(object sender, EventArgs e)
        {
            panel2.Controls.SetChildIndex((Arrow)sender, 0);    // マウスが矢印の中に入ったら、その矢印を最前面に移動する。
                        //(省略)
        }

2.6 矢印の上にマウスポインタを置き、左クリックでその矢印を削除するコード

 まず、左クリックをいたときにコンテキストメニューが開くようにするためには、クラス直下でContextMenuStripクラスの変数を宣言しなければならない。これは変数contextMenuStripOnArrowの中に、どのオブジェクトでコンテキストメニューが開かれたかの情報が含まれているからである。

    class RulerMain : Form
    {
                        //(省略)
        private ContextMenuStrip contextMenuStripOnArrow;     // 矢印のコンテキストメニュー
    }

 次に、RulaerMain()コンストラクタで、ContextMenuStripのインスタンスを作り、それにコンテキストメニューの「削除」のメニューを追加する。またその削除を選択した時に起動するイベントハンドラをtsmiDelete.Clickに登録する。

        private RulerMain()
        {
                        //(省略)
            // 矢印の上で表示するコンテキストメニューを作る
            contextMenuStripOnArrow = new ContextMenuStrip();
            ToolStripMenuItem tsmiDelete = new ToolStripMenuItem("削除(&D)");   // コンテキストメニューで表示される項目
            tsmiDelete.Click += new EventHandler(tsmiDelete_Click);             // コンテキストメニューの中で「削除」を選択した時のデリゲート
            contextMenuStripOnArrow.Items.Add(tsmiDelete);                      // コンテキストメニューにtsmiDelete(削除)を追加する
        }

tsmiDelete_Click(object sender, EventArgs e)イベントハンドラの中で、クラス直下で宣言したContextMenuStrip型の変数contextMenuStripOnArrowに対してSourceControlメソッドの操作を行う。この操作を行うと、どの矢印で削除を行おうとしているかがわかる。そのオブジェクト情報をArrow型のdeltingArrowに代入し、Controls.Remove()メソッドで、panel2の表示から消すことができる。

        // Arrowの上でコンテキストメニューの「削除」を選択した時のイベントハンドラ
        private void tsmiDelete_Click(object sender, EventArgs e)
        {
            Arrow deletingArrow = contextMenuStripOnArrow.SourceControl as Arrow;// コンテキストメニューを開いて削除を選択した矢印をdeletingArrowに代入する。as Arrowにより、deletingArrowはArrow型以外の時nullになる

            if (deletingArrow != null)      // deletingArrowはArrow型以外の時nullになる
            {
                panel2.Controls.Remove(deletingArrow);  // panel2に登録されたArrow型のオブジェクトを消す
            }
            else MessageBox.Show("選択したのはArrow型ではありません!"); 
        }

#3.ソースコード
RulerMainクラスの全ソースコードを以下に示す。2.1項で示したArrow.csと合わせて、参照してください。

RulerMain.cs
using System;
using System.Drawing;
using System.Windows.Forms;

namespace Ruler
{
    class RulerMain : Form
    {
        public static void Main()
        {
            Application.Run(new RulerMain());
        }

        private Panel panel1;               // 定規を配置するパネル
        private Panel panel2;               // 定規のメモリを指す矢印を配置するパネル

        private PictureBox pictureBox1;     // 定規を描画するpictureBox
        private PictureBox baseArrow;     // 矢印タグのホーム
        private PictureBox cursorLinePictureBox;     // 定規の目盛りを読むためのカーソル

        private Button button1;     // 拡大ボタン
        private Button button2;     // 縮小ボタン
        private Label label1;       // 矢印が指している目盛りの値を表示するラベル


        private const int MAXGRADUATIONVALUE = 30;          // 定規の最大値
        private const int MINIMUMGRADUATIONPERLARGE = 10;   // 大きい目盛りの分割数
        private const int PIXELPERMINIMUMGRADUATION = 5;    // 最小メモリのピクセル数
        private const int RULERWIDTH = 50;                  // 定規の幅
        private const int PIXELPERBLANK = 15;               // 定規の左右にある余白のピクセル数
        private const int FOUNDAMENTALgRADUATIN = 10;       // 一番短い目盛りの線の長さ
        private double scale = 1.0;                         // 定規の表示を拡大するための倍率

        private const int ArrowWidth = 21;                  // 矢印の幅
        private const int ArrowHeight = 44;                 // 矢印の高さ
        private Size arrowSize = new Size(ArrowWidth, ArrowHeight);     // 矢印のサイズ
        private int ArrowCenterX = ArrowWidth / 2;          // 矢印の幅(X軸)の中心の値

        private ContextMenuStrip contextMenuStripOnArrow;     // 矢印のコンテキストメニュー


        private RulerMain()
        {


            // 定規を表示するPictueBox1の定義 
            this.pictureBox1 = new PictureBox();
            this.pictureBox1.Size = new Size((MAXGRADUATIONVALUE * MINIMUMGRADUATIONPERLARGE * PIXELPERMINIMUMGRADUATION + 2 * PIXELPERBLANK + 1), RULERWIDTH);
            this.pictureBox1.Location = new Point(0, 0);
            this.pictureBox1.Image = MakeRuler(MAXGRADUATIONVALUE, MINIMUMGRADUATIONPERLARGE, PIXELPERMINIMUMGRADUATION, PIXELPERBLANK, RULERWIDTH);
            this.pictureBox1.BorderStyle = BorderStyle.None;   //境界線
            this.pictureBox1.BackColor = Color.LightBlue;


            // 定規を乗せるパネル panel1の定義
            this.panel1 = new Panel();
            this.panel1.BorderStyle = BorderStyle.FixedSingle;   //境界線
            this.panel1.Location = new Point(10, 5);
            this.panel1.Size = new Size((MAXGRADUATIONVALUE * MINIMUMGRADUATIONPERLARGE * PIXELPERMINIMUMGRADUATION + 2 * PIXELPERBLANK + 1) + 2, 125);
            this.panel1.BackColor = Color.MintCream;
            this.panel1.AutoScroll = true;
            this.panel1.Controls.Add(pictureBox1);


            // 黄色い矢印の定位置を表示する矢印の定義 
            this.baseArrow = new Arrow();
            this.baseArrow.Location = new Point(250, 155);
            this.baseArrow.MouseDown += new MouseEventHandler(baseArrow_MouseDown);
            this.baseArrow.MouseMove += new MouseEventHandler(baseArrow_MouseMove);
            this.baseArrow.MouseUp += new MouseEventHandler(baseArrow_MouseUp);


            // 定規のメモリを指す矢印を配置するパネル
            this.panel2 = new Panel();
            this.panel2.Location = new Point(pictureBox1.Location.X, pictureBox1.Location.Y + pictureBox1.Height);
            this.panel2.Size = new Size(pictureBox1.Width, baseArrow.Height + 10);
            this.panel2.BorderStyle = BorderStyle.None;
            this.panel2.BackColor = Color.LightGreen;
            this.panel1.Controls.Add(panel2);


            // 拡大ボタン(bottun1)の定義
            this.button1 = new Button();
            this.button1.Location = new Point(10, 170);
            this.button1.Size = new Size(100, 30);
            this.button1.Text = "拡大";
            this.button1.Click += new EventHandler(button1_click);


            // 縮小ボタン(bottun2)の定義
            this.button2 = new Button();
            this.button2.Location = new Point(130, 170);
            this.button2.Size = new Size(100, 30);
            this.button2.Text = "縮小";
            this.button2.Click += new EventHandler(button2_click);


            // 定規の目盛りを読むためのカーソルの定義
            this.cursorLinePictureBox = new PictureBox();
            this.cursorLinePictureBox.Size = new Size(1, pictureBox1.Height);
            this.cursorLinePictureBox.BackColor = Color.Red;


            // Form1の定義
            this.Text = "定規";
            //this.Size = new Size(1600, 480);
            this.Size = new Size(panel1.Width + 37, 280);
            this.MinimumSize = new Size(panel1.Width + 37, 280);
            this.MaximumSize = new Size(panel1.Width + 37, 280);
            this.Controls.Add(this.panel1);
            this.Controls.Add(baseArrow);
            this.Controls.Add(button1);
            this.Controls.Add(button2);


            // 矢印の上で表示するコンテキストメニューを作る
            contextMenuStripOnArrow = new ContextMenuStrip();
            ToolStripMenuItem tsmiDelete = new ToolStripMenuItem("削除(&D)");   // コンテキストメニューで表示される項目
            tsmiDelete.Click += new EventHandler(tsmiDelete_Click);             // コンテキストメニューの中で「削除」を選択した時のデリゲート
            contextMenuStripOnArrow.Items.Add(tsmiDelete);                      // コンテキストメニューにtsmiDelete(削除)を追加する
        }


        //
        // 定規のBitmapを画くメソッド
        private Image MakeRuler(int maxGraduationValue, int minimumGraduationPerLarge, int pixelPerMinimumGraduation, int pixelPerBlank, int rulerWidth)
        {
            int graduationLength = 10;
            Bitmap b = new Bitmap(maxGraduationValue * minimumGraduationPerLarge * pixelPerMinimumGraduation + 2 * pixelPerBlank, rulerWidth);
            Pen dp = new Pen(Color.Black, 1);

            using (var g = Graphics.FromImage(b))
            {
                for (int x1 = 0; x1 <= maxGraduationValue; x1++)
                {
                    int x2Max;
                    if (x1 < maxGraduationValue - 1) x2Max = minimumGraduationPerLarge;
                    else x2Max = minimumGraduationPerLarge + 1;

                    for (int x2 = 0; x2 < x2Max && x1 < maxGraduationValue; x2++)
                    {
                        if (x2 == 0 || x2 == 10)
                        {
                            graduationLength = FOUNDAMENTALgRADUATIN * 3;
                        }
                        else if (x2 == 5)
                        {
                            graduationLength = FOUNDAMENTALgRADUATIN * 2;
                        }
                        else graduationLength = FOUNDAMENTALgRADUATIN;

                        g.DrawLine(dp, (x1 * minimumGraduationPerLarge + x2)
                                                 * pixelPerMinimumGraduation + pixelPerBlank,
                                       b.Height,
                                       (x1 * minimumGraduationPerLarge + x2)
                                                 * pixelPerMinimumGraduation + pixelPerBlank,
                                           b.Height - graduationLength);
                    }

                    int letterPositionOffset;   // 定規の目盛りの値を書く位置のオフセット

                    if (x1 < 10)
                    {
                        letterPositionOffset = pixelPerBlank - 8;
                    }
                    else
                    {
                        letterPositionOffset = pixelPerBlank - 14;
                    }

                    g.DrawString(x1.ToString(),
                                 new Font("MS UI Gothic", 16),
                                 Brushes.Blue,
                                 x1 * minimumGraduationPerLarge * pixelPerMinimumGraduation
                                        + letterPositionOffset,
                                 0,
                                 new StringFormat());
                }
            }
            return b;
        }

        //
        // 拡大ボタンを押したときの処理
        private void button1_click(Object sender, EventArgs e)
        {
            panel2.Controls.Remove(label1);

            scale = scale * 2.0;

            this.pictureBox1.Size = new Size((MAXGRADUATIONVALUE * MINIMUMGRADUATIONPERLARGE * (int)(PIXELPERMINIMUMGRADUATION * scale) + 2 * PIXELPERBLANK + 1), RULERWIDTH);

            this.pictureBox1.Image = MakeRuler(MAXGRADUATIONVALUE, MINIMUMGRADUATIONPERLARGE, (int)(PIXELPERMINIMUMGRADUATION * scale), PIXELPERBLANK, RULERWIDTH);

            this.panel2.Size = new Size(pictureBox1.Width, baseArrow.Height + 10);

            foreach (Control c in panel2.Controls)
            {
                int x = (int)(((Arrow)c).Graduation * MINIMUMGRADUATIONPERLARGE * (int)(PIXELPERMINIMUMGRADUATION * scale)) + PIXELPERBLANK - 10;
                Point p = new Point(x, 0);
                c.Location = p;
            }
        }

        //
        // 縮小ボタンを押したときの処理
        private void button2_click(Object sender, EventArgs e)
        {
            panel2.Controls.Remove(label1);

            scale = scale / 2.0;
            if (scale < 1.0) scale = 1.0;   // 倍率が1.0以下にならないようにする

            this.pictureBox1.Size = new Size((MAXGRADUATIONVALUE * MINIMUMGRADUATIONPERLARGE * (int)(PIXELPERMINIMUMGRADUATION * scale) + 2 * PIXELPERBLANK + 1), RULERWIDTH);

            this.pictureBox1.Image = MakeRuler(MAXGRADUATIONVALUE, MINIMUMGRADUATIONPERLARGE, (int)(PIXELPERMINIMUMGRADUATION * scale), PIXELPERBLANK, RULERWIDTH);

            this.panel2.Size = new Size(pictureBox1.Width, baseArrow.Height + 10);

            foreach (Control c in panel2.Controls)
            {
                int x = (int)(((Arrow)c).Graduation * MINIMUMGRADUATIONPERLARGE * (int)(PIXELPERMINIMUMGRADUATION * scale)) + PIXELPERBLANK - 10;
                Point p = new Point(x, 0);
                c.Location = p;
            }
        }

        Arrow movingArrow;     // メモリを指し示す矢印のArrow(灰色)
        bool isDragPictureBox = false;    // メモリを指し示す矢印がドラッグされているかを判定する変数


        //
        // 黄色のpictureBox2をクリックした時の操作
        private void baseArrow_MouseDown(Object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)  // 左クリックをした時
            {
                isDragPictureBox = true;
                movingArrow = new Arrow();

                this.Controls.Add(movingArrow);
                this.Controls.SetChildIndex(movingArrow, 0);
                this.movingArrow.Image = Properties.Resources.VMoveTag;
                this.movingArrow.Location = ConvertDrugedArrowCoordinates(e, baseArrow, this);
            }
        }

        // MouseEventArgs eで与えられるマウスポインタの点をマウスの座標系であるc1から、座標系c2に変換しPoint型で返す。
        private Point ConvertArrowCoordinates(MouseEventArgs e, Control c1, Control c2)
        {
            return ConvertArrowCoordinates(e, c1, c2, 0, 0);
        }

        // MouseEventArgs eで与えられる点をマウスポインタが矢印をの座標を(ArrowCenterX, 10)の位置でドラッグするようにオフセットし、座標系をマウスのあるc1から、座標系c2に変換し、Point型で返す。
        private Point ConvertDrugedArrowCoordinates(MouseEventArgs e, Control c1, Control c2)
        {
            return ConvertArrowCoordinates(e, c1, c2, ArrowCenterX, 10);
        }


        //
        // MouseEventArgs eで与えられるマウスポインタの座標をオフセット(xOffset, yOffset)だけずらし、座標系をマウスのあるc1から、座標系c2に変換し、Point型で返す。
        private Point ConvertArrowCoordinates(MouseEventArgs e, Control c1, Control c2, int xOffest, int yOffset)
        {
            Point pTemp = new Point();  // マウスポインタの値ーオフセットの値が代入される
            Point pInC1;                // C1(マウス)の座標系
            Point pInC2;                // C2の座標系

            pTemp.X = e.X - xOffest; // ArrowCenterX(=11)は矢印のx軸の原点をずらすため(マウスポインタが矢印のX軸の中心を指すようにするため)
            pTemp.Y = e.Y - yOffset;
            pInC1 = c1.PointToScreen(pTemp);
            pInC2 = c2.PointToClient(pInC1);

            return pInC2;
        }


        //
        // 初期位置にある矢印をドラッグしながら移動させる為のメソッド
        private void baseArrow_MouseMove(Object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)  // 左クリックをした時
            {
                this.ActiveControl = null; // フォーカスされているコントロールのフォーカスを解除(これをしないとbaseArrowをクリックしたときに、スクロールで隠れているフォーカスされている矢印が、矢印の移動とともに表示されてしまう。

                if (isDragPictureBox)
                {
                    // マウスの位置を座標変換した後、矢印の位置を変換する
                    this.movingArrow.Location = ConvertDrugedArrowCoordinates(e, baseArrow, this);
                }
            }
        }


        //
        //
        private int count = 0;  // 矢印に通し番頭を与えるためのカウンタ

        //
        // 矢印を定規の矢印配置エリアにドロップするためのメソッド
        private void baseArrow_MouseUp(Object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)  // 左クリックをした時
            {
                Point pTemp;
                Point pMouse = new Point();
                pMouse.X = e.X;
                pMouse.Y = e.Y;

                //
                //
                // 矢印がドロップできる領域に入っているかをマウスの位置から判定し、ドロップする
                if (IsInArea(baseArrow, pMouse, panel2, new Point(panel2.Location.X + PIXELPERBLANK, -FOUNDAMENTALgRADUATIN + 10), new Point(panel2.Width - PIXELPERBLANK, FOUNDAMENTALgRADUATIN + 10)))
                {
                    pTemp = ConvertDrugedArrowCoordinates(e, baseArrow, panel2);
                    pTemp.Y = 0;

                    // 矢印をドロップしたときに新しい矢印を作り、panel2に配置する
                    Arrow arrow = new Arrow();
                    arrow.Tag = count;
                    arrow.Location = pTemp;
                    // 矢印にデリゲートによってイベントハンドラを登録する
                    arrow.DoubleClick += arrow_DoubleClick;     // 重なったarrowの順番を変える
                    arrow.MouseEnter += arrow_MouseEnter;       // 矢印が示す指示値を表示する
                    arrow.MouseLeave += arrow_MouseLeave;       // 矢印が示す指示値を隠す
                    arrow.MouseDown += arrow_MouseDown;         // 矢印の位置を変えるためにドラッグする
                    arrow.MouseMove += arrow_MouseMove;         // 矢印の位置を変える
                    arrow.MouseUp += arrow_MouseUp;             // 矢印の位置を定める
                    arrow.KeyDown += arrow_KeyDown;             // 矢印を矢印キーで動かすためのデリゲート
                    arrow.KeyUp += arrow_keyUp;                 // 矢印を動かした矢印キーを離したときのデリゲート

                    panel2.Controls.Add(arrow);

                    // 矢印が置かれた場所を目盛りの値に変換する
                    double x = (arrow.Location.X - PIXELPERBLANK + ArrowCenterX) / (double)(MINIMUMGRADUATIONPERLARGE * (int)(PIXELPERMINIMUMGRADUATION * scale));
                    arrow.Graduation = x;   // 矢印のGraduationプロパティに目盛りの値を与える

                    arrow.Focus();  // 矢印をフォーカスし、矢印キーで移動できるようにする。

                    arrow.ContextMenuStrip = contextMenuStripOnArrow;       // 矢印ArrowのContextMenuStripプロパティーにコンテキストメニューに書かれた内容であるContextMenuStrip型のインスタンスを指定する。矢印の上で左クリックしたときに、ContextMenuStrip型のインスタンスであるcontextMenuStripOnArrowに登録されたコンテキストメニューが表示されるようになる。

                    ++count;       // 矢印(の arrow.Tag = count;)に通し番号を与えるためのカウンタのカウントアップ
                }

                // フォームのコントロールからmovedPictueBoxを削除する。
                this.Controls.Remove(movingArrow);

                isDragPictureBox = false;
            }
        }

        //
        // 矢印をダブルクリックしたときに、その矢印を最背面に移す
        private void arrow_DoubleClick(object sender, EventArgs e)
        {
            this.ActiveControl = null; 
            panel2.Controls.Remove((Arrow)sender);
            panel2.Controls.Add((Arrow)sender);
        }


        //
        //
        private void arrow_MouseEnter(object sender, EventArgs e)
        {
            panel2.Controls.SetChildIndex((Arrow)sender, 0);    // マウスが矢印の中に入ったら、その矢印を最前面に移動する。

            showGraduation(sender);     // 矢印が示している目盛りの値を表示する

        }

        //
        // 矢印の横に、矢印が示している値を表示するメソッド
        private void showGraduation(object sender)
        {
            panel2.Controls.Remove(label1); //残像として残っているlabel1を消去する

            Point pArrow = ((Arrow)sender).Location;
            object tagNum = ((Arrow)sender).Tag;

            // マウスポインタの入った矢印の座標を取得し、位置を表示するラベルのLocationに変換する
            Point p1 = new Point(); // label1の位置
            label1 = new Label();
            label1.AutoSize = true;
            label1.Text = $"矢印{tagNum}の位置は{((Arrow)sender).Graduation}です。";

            // 矢印が示す値を表示するlabe1が、矢印を表示する領域panel2からはみ出さないようにする。はみ出しそうになるとlabel1が矢印の左側に表示される。
            if (panel1.PointToClient(panel2.PointToScreen(pArrow)).X < panel1.Width - (label1.Width + PIXELPERBLANK + ArrowWidth + 23))
            {
                label1.TextAlign = ContentAlignment.TopLeft;
                p1.X = pArrow.X + 23;
            }
            else
            {
                label1.TextAlign = ContentAlignment.TopRight;
                p1.X = pArrow.X - label1.Width - 39;
            }

            p1.Y = pArrow.Y + 5;    // ラベルを表示する位置のy成分を与える。矢印のトップより5ピクセルだけ下にする。
            label1.Location = p1;

            panel2.Controls.Add(label1);
            panel2.Controls.SetChildIndex(label1, 0);   // ラベルを最前面にする

            // カーソルラインをpictureBox1上に表示する
            cursorLinePictureBox.Location = new Point(pArrow.X+ArrowCenterX, 0);
            pictureBox1.Controls.Add(cursorLinePictureBox);
        }


        //
        // マウスカーソルが矢印から離れたとき
        private void arrow_MouseLeave(object sender, EventArgs e)
        {
            removeReadingAndCursor();       // 矢印の読み取り値とカーソルを消す
        }


        // 矢印の読み取り値とカーソルを消す
        private void removeReadingAndCursor()
        {
            // 矢印の位置を表示するラベルを消す
            panel2.Controls.Remove(label1);

            // カーソルラインをpictureBox1上から消す。
            pictureBox1.Controls.Remove(cursorLinePictureBox);
        }


        //
        //
        // マウスポインタが指定のエリアに入っているか判別するメソッド
        private static bool IsInArea(Control c1, Point pMouse, Control c2, Point pStart, Point pEnd)
        {
            // スクリーン座標に変換
            Point pMouseOnScreen = c1.PointToScreen(pMouse);    // マウスの位置
            Point pStartOnScreen = c2.PointToScreen(pStart);    // 判定エリアの始点
            Point pEndOnScreen = c2.PointToScreen(pEnd);        // 判定エリアの終点

            Rectangle Rect = new Rectangle();
            Rect = Rectangle.FromLTRB(pStartOnScreen.X, pStartOnScreen.Y, pEndOnScreen.X, pEndOnScreen.Y);

            // 判定エリアに入っているか判定
            return Rect.Contains(pMouseOnScreen);
        }

        //
        // 矢印をドラッグするメソッド群 arrow_MouseDown()から_MouseUp()まで
        private void arrow_MouseDown(Object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)  // 左クリックをした時
            {
                slipOffset = e.X - ArrowCenterX;// ドラッグしたときに矢印がスリップすることを補正ためのオフセット、arrow_MouseMove()で補正
                isDragPictureBox = true;

                // 矢印の位置を表示するラベルを消す
                panel2.Controls.Remove(label1);
            }
        }

        //
        //
        int slipOffset;     // ドラッグしたときにマウスポインタと矢印の中心線がずれていることによっておこる矢印のスリップを補正ためのオフセット

        //
        // マウスの移動に伴い、矢印を移動する
        private void arrow_MouseMove(Object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)  // 左クリックをした時
            {
                Point p = new Point();

                if (isDragPictureBox)
                {
                    p = ConvertDrugedArrowCoordinates(e, (Arrow)sender, panel2);    // マウスポインタの位置をドラッグした矢印の座標系から、panel2の座標系に変換する
                    p.X = p.X - slipOffset;     // slipOffsetはドラッグしたときに矢印がスリップすることを補正
                    p.Y = 0;                    // 矢印の位置を矢印配置パネル(panel2)のtopに固定する。

                    moveArrow(sender, p);       // マウスの位置pに従って、矢印を移動する
                    showGraduation(sender);         // 矢印が指している目盛りを表示する
                }
            }
        }

        //
        // 矢印をドロップする
        private void arrow_MouseUp(Object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)  // 左クリックをした時
            {
                isDragPictureBox = false;

                // 矢印をドロップしたマウスの位置から、矢印が示している目盛りの値を計算する
                double x = (((Arrow)sender).Location.X - PIXELPERBLANK + ArrowCenterX) / (double)(MINIMUMGRADUATIONPERLARGE * (int)(PIXELPERMINIMUMGRADUATION * scale));

                ((Arrow)sender).Graduation = x;     // 矢印のが示している目盛りの値を矢印オブジェクトに書き込む

                ((Arrow)sender).Focus();            // 矢印を矢印キーで操作(移動)できるように、フォーカスする
            }
        }

        //
        // 矢印arrowがフォーカスされているときに、矢印キーを押すと当該矢印arrowが移動する。
        private void arrow_KeyDown(object sender, KeyEventArgs e)
        {
            Point p = ((Arrow)sender).Location;

            if (e.KeyCode == Keys.Right)
            {
                p.X++;
            }
            else if (e.KeyCode == Keys.Left)
            {
                p.X--;
            }

            moveArrow(sender, p);       // 矢印をpへ移動する
            showGraduation(sender);     // 矢印が示している目盛りの値を表示
        }


        //
        // 矢印キーを離したときに目盛りの読み値のlabelとカーソルラインを消す
        private void arrow_keyUp(object sender, KeyEventArgs e)
        {
            removeReadingAndCursor();       // 矢印の読み取り値とカーソルを消す

        }

        //
        // 矢印を指定した場所pに移動するメソッド
        private void moveArrow(object sender, Point p)
        {
            Point p1 = new Point();     // p1は矢印の先端を表す点に使用する
            p1.X = p.X + ArrowCenterX;  // 矢印の先端を表す点(pが矢印のLocationであるからそこから、ArrowCenter分だけ右にずらし矢印の先端を表す。
            p1.Y = 0;
            Point p1Screen = (panel2).PointToScreen(p1);      // p1をスクリーン座標に変換
            int p1ScreenX = p1Screen.X;

            Point panel2Screen = panel1.PointToScreen(panel2.Location);                             // panel2の位置をスクリーン座標に変換
            int rulerZeroPositionXInScreen = panel2Screen.X + PIXELPERBLANK;                        // スクリーン座標表示の目盛りゼロの位置のX成分
            int ruler30PositionXInScreen = panel2Screen.X + panel2.Size.Width - PIXELPERBLANK;      // スクリーン座標表示の目盛り30の位置のX成分


            if (p1ScreenX < rulerZeroPositionXInScreen)                 // 移動先が目盛りのゼロの位置より小さい値か判定
            {
                p.X = PIXELPERBLANK - ArrowCenterX;                       // 矢印がゼロを指す位置に強制的に戻す
            }
            else if (p1ScreenX > ruler30PositionXInScreen)              // 移動先が目盛りの30の位置より小さい値か判定
            {
                p.X = panel2.Size.Width - PIXELPERBLANK - ArrowCenterX;  // 矢印がゼロを指す位置に強制的に戻す
            }

            // 矢印の位置を変更し、矢印を動かす
            ((Arrow)sender).Location = p;

            // 矢印の現在の位置を目盛りの値に換算し、矢印のパラメータに代入する
            ((Arrow)sender).Graduation
                  = (((Arrow)sender).Location.X - PIXELPERBLANK + ArrowCenterX) / (double)(MINIMUMGRADUATIONPERLARGE * (int)(PIXELPERMINIMUMGRADUATION * scale));



            // 矢印がpanel1右サイドを範囲を超えたら、スクロールバーをずらす
            if (panel2.PointToScreen(p).X > (this.PointToScreen(panel1.Location)).X + panel1.Size.Width - arrowSize.Width)
            {
                Point p2;
                p2 = panel1.AutoScrollPosition;
                p2.X = -panel1.AutoScrollPosition.X + 10;
                panel1.AutoScrollPosition = p2;
            }

            // 矢印がpanel1左サイドを範囲を超えたら、スクロールバーをずらす
            if (panel2.PointToScreen(p).X < (this.PointToScreen(panel1.Location)).X)
            {
                Point p2;
                p2 = panel1.AutoScrollPosition;
                p2.X = -panel1.AutoScrollPosition.X - 10;
                panel1.AutoScrollPosition = p2;
            }
        }

        // Arrowの上でコンテキストメニューの「削除」を選択した時のイベントハンドラ
        private void tsmiDelete_Click(object sender, EventArgs e)
        {
            Arrow deletingArrow = contextMenuStripOnArrow.SourceControl as Arrow;// コンテキストメニューを開いて削除を選択した矢印をdeletingArrowに代入する。as Arrowにより、deletingArrowはArrow型以外の時nullになる

            if (deletingArrow != null)      // deletingArrowはArrow型以外の時nullになる
            {
                panel2.Controls.Remove(deletingArrow);  // panel2に登録されたArrow型のオブジェクトを消す
            }
            else MessageBox.Show("選択したのはArrow型ではありません!"); 
        }
    }
}

VNoneTag.png

VMoveTag.png

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

C# Azure Storageでシステムログ(.log)をダウンロードする

logファイルの種類が追加Blob(AppendBlob)の場合は、CloudAppendBlobを使用する。imageの場合は、ブロックBlob(BlockBlob)であったので、CloudBlockBlobを使用。image fileと同じだと思って、CloudBlockBlobを使おうとしたら以下のエラーメッセージがでて少しはまった。

Blob type of the blob reference doesn't match blob type of the blob

image.png

public static void DownloadLog(string filename)
{
    CloudBlobContainer container = GetBlobContainer("log");

    // ダウンロードするファイル名を指定
    CloudAppendBlob appendBlob_download = container.GetAppendBlobReference(filename);

    //ダウンロード後のパスとファイル名を指定。
    string path = Directory.GetCurrentDirectory() + "\\log\\" + DateTime.Now.ToString("yyyyMMdd") + ".log";

    appendBlob_download.DownloadToFile(path, FileMode.CreateNew);

}
private static CloudBlobContainer GetBlobContainer(string folder)
{
    // Retrieve storage account from connection string.
    CloudStorageAccount storageAccount = CloudStorageAccount.Parse(
        CloudConfigurationManager.GetSetting("StorageConnectionString"));

    // Create the blob client.
    CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();

    // Retrieve a reference to a container.
    CloudBlobContainer container = blobClient.GetContainerReference(folder);

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

Easy Auth で認証したユーザIDを入手する

Easy Auth で認証したユーザIDを入手する

Azure App Service に組み込まれた Easy Auth を使った Azure AD 認証連携、設定が簡単すぎて感動的. ただ、User.Identity.Name が空だったのでコードを書いた.

Visual Studio 2019 の新しいプロジェクトの作成で「ASP.NET Core Web アプリケーション」を選び、ASP.NET Core 3.1 の Web アプリケーション(モデル ビュー コントローラ) を選んで生成されるソースコードがまず出発地点.

次に Startup.cs を開き、先頭に using System.Security.Principal; を追加して、Configure メソッドの app.UseAuthorization();app.UseEndpoints(endpoints => の間に

app.Use(async (context, next) => {
    var identity = new GenericIdentity(context.Request.Headers["X-MS-CLIENT-PRINCIPAL-NAME"][0]);
    context.User = new GenericPrincipal(identity, null);
    await next.Invoke();
});

を埋め込む. 要するに Easy Auth はリクエストヘッダの X-MS-CLIENT-PRINCIPAL-NAME にユーザ ID を入れてくれている.

あとは Views/Home/Index.cshtml を開いて <h1 class="display-4">Welcome</h1><h1 class="display-4">Welcome @User.Identity.Name</h1> に書き換えるだけ.

デプロイして、確認. 画面に Azure AD にログインしたユーザ ID が表示された. OK!!

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

CからC#のDLLを呼び出した時の文字列の受け渡しについて

VB.netで作成されたDLLが提供されているのですが、アンマネージドCから呼び出すにもDLLExportされていないので、C#でDLLを作成し、VB.netのDLLを外部参照して呼び出した結果をC#のDLLの中で呼び出し元に返すようにしました。
C#でDLLExportしてdumpbinでexportされていることは確認し、CからLoadLibrary、GetProcAddressにてC#のDLLを呼び出せるようになりました。呼び出し元のCに返す内容としてはint型で結果コードとstring型でエラーメッセージがあるのですが、結果コードは取得できるのですが、c#のstring型で定義しているエラーメッセージがC側でうまく表示されません。(文字化けしたような表示になります)
C#でstring型はCOMスタイルのBSTR型のような先頭に文字列長の情報が入っているので変換が必要と思っているのですがどのような変換を行えばよいかご教授いただけますでしょうか?

c#DLL作成環境
windows10 64bit
VS2019 + BuildTool2015
nugetパッケージunmanagedexports.1.2.7を使用

using System;
using System.Runtime.InteropServices;
using System.Text;
using VBDLL;
using RGiesecke.DllExport;
namespace CS_VBDLL
{
public class clsCS_VBDLL
{
static VBDLL.ClassReturn retCR;
static VBDLL.VBDLL func = new VBDLL.VBDLL();
[DllExport("ConnectStatus", CallingConvention = CallingConvention.StdCall)]
public static int GetStatus(int chNo, out string mes)
{
retCR = func.GETSTATUS(ch);
mes = retCR.error_message;
return retCR.return_code;
}
}
}

C(呼び出し元)側
windows10 64bit
#include <windows.h>
int main()
{
int ret;
char mes[256];
HINSTANCE handle_Dll = LoadLibrary(TEXT("D:\exec\CS_VBDLL.dll"));
if (!handle_Dll) {
break;
}
GetStatus getstatus = (GetStatus)(GetProcAddress(handle_Dll, "GetStatus"));
ret = connectstatus(1,mes);
printf("ret=%d\n", ret);
printf("mes=%s\n", mes);
if (handle_Dll) {
FreeLibrary(handle_Dll);
}
}

ret=-1
mes=

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

Vfwなcodecの設定画面を呼びたい

動機

現在、Caputraというデスクトップキャプチャソフトに、AviUtlを使っている人ならおなじみの可逆圧縮codecである、Ut Video Codec Suiteを対応させる試みを行っています

その過程で、codecの設定をわざわざレジストリをいじるのは面倒だし、設定画面とか呼べないの?と思ったのですね。

例えばAviUtlなら
image.png
この設定ボタンから呼べる
image.png
こういうやつです。

このウィンドウはAviUtlに固有のものではなくて他のソフトからも同じものが出るということは、Ut Video Codec Suite側で生成しているものだと推測できます。

Ut Video Codec Suiteの実装を読む

幸いなことにソースコードはGitHubで公開されているので読んでいきます。

https://github.com/umezawatakeshi/utvideo

するとそういえばVFW pluginだったなと思い出してきました。

VfwというのはVideo for Windowsの略で、Win32API触ったことある人ならわかるであろう、プロージャーを書いてメッセージを受け取っていくタイプの書き方で、動画のエンコードやデコードなどなどを実装したdllを登録することで、いろいろなソフトウェアからそれが呼べる仕組みのことです。Windows Media Playerなんかはこれをつかって再生していたと思います。Windows 10標準搭載の動画アプリはMicrosoft Media Foundationという別のものを使っていますが。

なんか検索してると某有名ゲームエンジンの関連のVexe Frameworkのほうが引っかかってくるようですが・・・。

設定画面の呼び出し

いろいろ読んでいくと、次のような箇所を見つけました。つまりICM_CONFIGUREを受け取ったプロージャーは、DialogBoxParamによってWindowを生成しているではないですか。

utv_vcm/DriverProc.cpp
    case ICM_CONFIGURE:
        if (lParam1 == -1)
            return pCodec->QueryConfigure();
        else
            return pCodec->Configure((HWND)lParam1);

https://github.com/umezawatakeshi/utvideo/blob/077a6a6202a3b5d60d1716559759210f6cedd795/utv_vcm/DriverProc.cpp#L45-L49

utv_vcm/VCMCodec.cpp
LRESULT CVCMCodec::Configure(HWND hwnd)
{
    if (m_mode != ICMODE_COMPRESS)
        return ICERR_UNSUPPORTED;

    return m_pCodec->Configure(hwnd) == 0 ? ICERR_OK : ICERR_UNSUPPORTED;
}

https://github.com/umezawatakeshi/utvideo/blob/077a6a6202a3b5d60d1716559759210f6cedd795/utv_vcm/VCMCodec.cpp#L127-L133

utv_core/UL00Codec.cpp
INT_PTR CUL00Codec::Configure(HWND hwnd)
{
    DialogBoxParam(hModule, MAKEINTRESOURCE(IDD_UL00_CONFIG), hwnd, DialogProc, (LPARAM)this);
    return 0;
}

https://github.com/umezawatakeshi/utvideo/blob/077a6a6202a3b5d60d1716559759210f6cedd795/utv_core/UL00Codec.cpp#L31-L35

つまりVfw codecに対してICM_CONFIGUREメッセージを投げられれば良さそうです。

ただし

  • m_mode != ICMODE_COMPRESSより、m_modeICMODE_COMPRESSでなければならない
  • lParam1 == -1より、親ウィンドウのハンドルが必須

初期化

しかし、その前にpCodecとかm_modeとかいう変数が見えます。これらはどうやって初期化されているのでしょうか?

LRESULT CALLBACK DriverProc(DWORD_PTR dwDriverId, HDRVR hdrvr, UINT uMsg, LPARAM lParam1, LPARAM lParam2)
{
    CVCMCodec *pCodec = (CVCMCodec *)dwDriverId;

変数の定義を見るだけでは、プロージャーの引数から渡ってくるんだなとしかわかりません。

utv_vcm/DriverProc.cpp
    case DRV_OPEN:
        return (LRESULT)CVCMCodec::Open((ICOPEN *)lParam2);

    case DRV_CLOSE:
        if (pCodec != NULL)
            delete pCodec;
        return TRUE;

https://github.com/umezawatakeshi/utvideo/blob/077a6a6202a3b5d60d1716559759210f6cedd795/utv_vcm/DriverProc.cpp#L26-L32

もう少し下を見るとDRV_CLOSEを受け取ったときに、pCodecdeleteしているところが見つかりました。さらにその上にDRV_OPENというそれっぽいのがあります。CVCMCodec::Openを見てみましょう。

utv_vcm/VCMCodec.cpp
CVCMCodec *CVCMCodec::Open(ICOPEN *icopen)
{
    if (IsLogWriterInitializedOrDebugBuild())
    {
        if (icopen != NULL)
            LOGPRINTF("CVCMCodec::Open(icopen=%p, icopen->fccType=%08X, icopen->fccHandler=%08X icopen->dwFlags=%08X)", icopen, icopen->fccType, icopen->fccHandler, icopen->dwFlags);
        else
            LOGPRINTF("CVCMCodec::Open(icopen=NULL)");
    }

    union
    {
        DWORD fccHandler;
        char fccChar[4];
    };

    DWORD icmode;

    if (icopen != NULL)
    {
        if (icopen->fccType != ICTYPE_VIDEO)
            return NULL;
        fccHandler = icopen->fccHandler;
        // なぜか小文字で渡されることがあるので、最初に大文字化しておく。
        for (int i = 0; i < 4; i++)
            fccChar[i] = toupper(fccChar[i]);

        icopen->dwError = ICERR_UNSUPPORTED;
        switch (icopen->dwFlags)
        {
        case ICMODE_COMPRESS:
            if (CheckInterfaceDisabledAndLog("VCM", "Encoder"))
                return NULL;
            break;
        case ICMODE_DECOMPRESS:
            if (CheckInterfaceDisabledAndLog("VCM", "Decoder"))
                return NULL;
            break;
        case ICMODE_QUERY:
            if (CheckInterfaceDisabledAndLog("VCM", "Query"))
                return NULL;
            break;
        default:
            return NULL;
        }

        icopen->dwError = ICERR_OK;
        icmode = icopen->dwFlags;
    }
    else
    {
        fccHandler = (DWORD)-1;
        icmode = 0;
    }

    return new CVCMCodec(fccHandler, icmode);
}

https://github.com/umezawatakeshi/utvideo/blob/077a6a6202a3b5d60d1716559759210f6cedd795/utv_vcm/VCMCodec.cpp#L31-L87

細かいところは読み飛ばすとして、まずicmode = icopen->dwFlags;が目に入ります。さっき言ってた変数ですね!さらにこの関数はreturn new CVCMCodec(fccHandler, icmode);してます。確かにDRV_OPENDRV_CLOSEは対応関係にあることがわかります。

どうやってVfwなcodecにメッセージを投げるか

つまり、どうにかしてコーデックのプロージャーにメッセージを投げられればいいわけです。

ただ、検索の仕方が悪くてなかなかたどり着けず、

https://github.com/baSSiLL/SharpAvi

のソースコードを読み漁ってました。

結局使う関数は

の3つだとわかりました。

ICOpenは次のようなプロトタイプ宣言で定義されています。

HIC VFWAPI ICOpen(
  DWORD fccType,
  DWORD fccHandler,
  UINT  wMode
);

fccというのが見えますが、これはFOURCCのことです。

FourCC - Wikipedia
FourCC (フォーシーシー) とは、データフォーマットを一意に識別するための4バイトの並びである(four-character code の意)。

fccTypeにはVIDCという文字列をFourCCとして整数に変換したもの、wModeには先に述べたようにICMODE_COMPRESSを渡すとして、fccHandlerには何を渡せばいいのでしょうか?今回はUt Video Codec Suiteの設定画面を呼びたいので、早速FourCCを調べましょう。

といっても公式で
Ut Video Codec Suite 21.2.0 readme (日本語)FourCC 一覧
にまとめられているのでそれを見るだけですね。

とりあえず今回はULH2にしましょう。

実装

arikitari na world!

親ウィンドウがないといけないので、雑にWPFで作りましょう。

MainWindow.xaml
<Window x:Class="WpfApp1.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="VfwConfigCall" Height="100" Width="300">
    <Grid>
        <Button Margin="10" Click="SettingClick">Setting</Button>
    </Grid>
</Window>
MainWindow.xaml.cs
using System.Windows;
namespace WpfApp1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
        private void SettingClick(object sender, RoutedEventArgs e)
        {
            MessageBox.Show("arikitari na world!");
        }
    }
}

まずはこれで全世界の人がまずは表示させるであろう恒例のarikitari na world!を表示させて動いたことを確認したら本実装に入りましょう。

定数

vfw.hをみつつ必要なものを取ってきましょう。

        private const int DRV_USER = 0x4000;
        private const int ICM_RESERVED_LOW = DRV_USER + 0x1000;
        private const int ICM_RESERVED = ICM_RESERVED_LOW;
        private const int ICM_CONFIGURE = ICM_RESERVED + 10;
        private const int ICMODE_COMPRESS = 1;

DLL読み込み

DLLを読まないと関数が呼べないので読み込んでおきましょう。

        private const string VFW_DLL = "msvfw32.dll";
        [DllImport(VFW_DLL, CallingConvention = CallingConvention.Winapi)]
        public static extern IntPtr ICOpen(uint fccType, uint fccHandler, int mode);
        [DllImport(VFW_DLL, CallingConvention = CallingConvention.Winapi)]
        public static extern int ICClose(IntPtr handle);
        [DllImport(VFW_DLL, CallingConvention = CallingConvention.Winapi)]
        public static extern int ICSendMessage(IntPtr handle, int message, IntPtr param1, IntPtr param2);

設定画面の呼び出し

あとはclickイベントハンドラを書き換えて設定画面を呼びましょう。FourCCの計算が面倒だったのでSharpAviをNuGetでプロジェクトに追加しておきます。

using System;
using System.Runtime.InteropServices;
using System.Windows;
using SharpAvi;

        private void SettingClick(object sender, RoutedEventArgs e)
        {
            var compressorHandle = ICOpen((uint)KnownFourCCs.CodecTypes.Video, (uint)new FourCC("ULH2"), ICMODE_COMPRESS);
            if (compressorHandle == IntPtr.Zero)
            {
                return;
            }
            try
            {
                var re = ICSendMessage(compressorHandle, ICM_CONFIGURE, new System.Windows.Interop.WindowInteropHelper(this).Handle, IntPtr.Zero);
            }
            finally
            {
                ICClose(compressorHandle);
            }
        }

結果

実行する前に忘れずにUt Video Codec Suiteをインストールしておきましょう。

或るプログラマの一生 » Ut Video Codec Suite

image.png

無事に呼び出せました。

余談: FourCCとcodecの関連付けの管理

FourCCとcodecの関連付けはレジストリで行われている。例えばUt Video Codec Suiteの場合、次のような項目が登録される。

REGEDIT4

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Drivers32]
"VIDC.ULRA"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.ULRG"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.ULY0"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.ULY2"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.ULY4"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.ULH0"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.ULH2"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.ULH4"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.UQY0"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.UQY2"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.UQRG"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.UQRA"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.UMRA"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.UMRG"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.UMY2"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.UMY4"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.UMH2"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.UMH4"="C:\\Windows\\system32\\utv_vcm.dll"

[HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows NT\CurrentVersion\Drivers32]
"VIDC.ULRA"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.ULRG"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.ULY0"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.ULY2"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.ULY4"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.ULH0"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.ULH2"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.ULH4"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.UQY0"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.UQY2"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.UQRG"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.UQRA"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.UMRA"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.UMRG"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.UMY2"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.UMY4"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.UMH2"="C:\\Windows\\system32\\utv_vcm.dll"
"VIDC.UMH4"="C:\\Windows\\system32\\utv_vcm.dll"

ではコーデックのあるFourCCを列挙するにはどうすればいいのか。レジストリを自分で読みたくはない。

どうやらICInfoという関数を使えばいいようだ。使い方のサンプルが
Locating and Opening Compressors and Decompressors - Win32 apps | Microsoft Docs
にあった。

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

C#でPDFファイルにパスワードがかかっているかどうかを確認したい

概要

タイトルの通り、C#でパスワードがかかっているかを判断したい!のメモ

empira/PDFsharp: A .NET library for processing PDF

環境

Visual Studio 2017
PDFsharp 12.0.1

準備

PDFsharpをnugetでインストールします。

パスワードがかかっているかチェックする

try
{
  // パスワードのチェックのみなので、ReadOnlyモードで開きます。
  PdfReader.Open(path, PdfDocumentOpenMode.ReadOnly).Dispose();
}
catch (PdfReaderException e)
{
  return e.Message;
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む