- 投稿日:2020-03-30T23:20:12+09:00
[C#]GeoTag(GPS情報)をjpgファイルに設定/取得するときのハマったメモ
もくじ
→https://qiita.com/tera1707/items/4fda73d86eded283ec4fGeoTag関連
- GeoTag(GPS情報)をjpgファイルに設定/取得する
- GeoTag(GPS情報)をjpgファイルに設定/取得するときのハマったメモやりたいこと
UWPのAPI(
GeotagHelper.SetGeotagAsync()
)を使って、jpegファイルにGeoTagを付与したいのだが、あるjpegファイルだけ、なぜかGeotagHelper.SetGeotagAsync()
を実行した際に例外が発生してしまう。
jpegファイルをちゃんとSetGeotagAsyncにセットしてやっているはずが、なんで例外になるのかさっぱりわからない。そのときに調べた(というかいろいろ試した)事のメモ。
結論(例外の原因)
原因は、GeoTag付与しようとしていたjpegファイルが、実はjpegファイルではなかったこと。
具体的には、そのjpegファイルをSystem.Drawing.Bitmap
のSave()
メソッドを使って作成していたが、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); }
- 投稿日:2020-03-30T23:17:14+09:00
反変性インタフェースと共変性インタフェース 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()); }
- 投稿日:2020-03-30T22:36:24+09:00
ASP.NET Core Blazor WebAssembly のハローワールド
Blazor WebAssembly を試してみようかなと思い立ったので以下のドキュメントを写経してみました。
プロジェクトテンプレートのインストール
今のところプレビューなので自前で入れます。.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 が生えます。
右下の ASP.NET Core hosted にチェックを入れると、ASP.NET Core のプロジェクトも作れて、そこに WebAssembly も入れてデプロイ出来るので Azure WebApps とかにデプロイするのが楽そうなので、それのチェックを入れて作ります。
新規作成すると、以下のようなテンプレートが生成されます。
この段階で ASP.NET Core 側の API を叩いて画面に表示する例のコードまで入ってい
るのはありがたいですね。Server 側プロジェクトにある WetherForecastController が WebAPI です。Client 側プロジェクトの Pages/FetchData.razor を見ると以下のように WetherForecast の URL 叩いています。いいね。
ローカル実行
何も考えずにローカル実行をするとブラウザーが立ち上がって WebAssembly の Blazor が動きますね。完璧。
Azure にデプロイ
Azure WebApps にデプロイしてみましょう。デプロイするのは Server 側のプロジェクトです。右クリックから発行を選びます。
適当に発行先を選んで(もしくは新規作成)デプロイをすると、本当にすんなりと動きます。
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 系の弱点の起動処理が重い点を除けば…。
- 投稿日:2020-03-30T20:03:02+09:00
役に立たない定規をつくる
次の写真に示す定規を作ってみた。何の役にも立たない定規だが、Formアプリケーションに必要な動作を幾つか組み込んである。
1.機能
この定規でできることは、次のとおりである。
(1)定規の目盛りを読むために黄色い矢印をドラッグして鶯色の領域に配置できる。
(2)この矢印は複数個配置できる。
(3)配置した矢印の上にマウスポインタを乗せると、その矢印が示している指示値を矢印の横に表示し、また目盛り上に赤いカーソルを表示する。
(4)表示された指示値は、通常矢印の右側に表示されるが、矢印が定規の右側の値を指示している時、指示値が見切れるのを防ぐため、矢印の左側に表示される。
(5)配置した矢印をドラッグして、指し示す位置を変えることができる。
(6)配置した矢印をマウスで選択して、矢印キー(←、→)で指し示す位置を変えることができる。
(7)少しずれて重なった2つの矢印の内、下側の矢印にマウスを重ねると前面に表示される。
(8)完全に重なった矢印をダブルクリックすると、一つ下に重なった矢印が前面に現れる。
(9)矢印の上にマウスポインタを置き、左クリックするとメニューが開き、削除の項目が表示され、これを選択するとその矢印が削除できる。
(10)定規の目盛りを拡大縮小できる。
2.機能を実現するために
組み込まれた機能の内、いくつかの機能についてコードを用いて説明する。
2.1 矢印の機能を盛り込んだArrowクラス
この定規を実現するために、まずArrowクラス(矢印クラス)を作った。Arrowクラスは、PictureBoxクラスを継承し、更に矢印が指し示す目盛りの値を記憶したおくGraduationプロパティを持っている。
Arrow.csusing 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; } } } }2.2 フォームの構造
この定規アプリケーションのメインクラスは、RulerMainクラスである。このクラスは、Formクラスを継承している。RulerMainクラスのフォームは、次のような構造になっている。まず第1層になるフォームの上にpanel1(下の図の鶯色の部分)、baseArrowと呼ばれるPictureBoxを継承したArrowクラスのインスタンス(縮小ボタンの右側の矢印)、拡大・縮小を行うためのbutton1と2の計4つのコントロールが配置されている。これらのコントロールは、RulerMain()コンストラクタで定義されている。
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をドラッグし、鶯色の領域でドロップするとその領域に配置できる。
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に配置した矢印の上にマウスポインタを置くと、矢印が示している値が表示される。
これは、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つの矢印の内、下側の矢印にマウスを重ねると前面に表示させるためのコード
下の図のように、下に重なった矢印の上にマウスポインタを重ねると、下側にあった矢印が上側になるようコードを工夫した。
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.csusing 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型ではありません!"); } } }
- 投稿日:2020-03-30T18:12:19+09:00
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 blobpublic 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; }
- 投稿日:2020-03-30T17:49:19+09:00
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!!
- 投稿日:2020-03-30T16:46:35+09:00
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=
- 投稿日:2020-03-30T16:34:27+09:00
Vfwなcodecの設定画面を呼びたい
動機
現在、Caputraというデスクトップキャプチャソフトに、AviUtlを使っている人ならおなじみの可逆圧縮codecである、Ut Video Codec Suiteを対応させる試みを行っています。
その過程で、codecの設定をわざわざレジストリをいじるのは面倒だし、設定画面とか呼べないの?と思ったのですね。
例えばAviUtlなら
この設定ボタンから呼べる
こういうやつです。このウィンドウは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.cppcase ICM_CONFIGURE: if (lParam1 == -1) return pCodec->QueryConfigure(); else return pCodec->Configure((HWND)lParam1);utv_vcm/VCMCodec.cppLRESULT CVCMCodec::Configure(HWND hwnd) { if (m_mode != ICMODE_COMPRESS) return ICERR_UNSUPPORTED; return m_pCodec->Configure(hwnd) == 0 ? ICERR_OK : ICERR_UNSUPPORTED; }utv_core/UL00Codec.cppINT_PTR CUL00Codec::Configure(HWND hwnd) { DialogBoxParam(hModule, MAKEINTRESOURCE(IDD_UL00_CONFIG), hwnd, DialogProc, (LPARAM)this); return 0; }つまりVfw codecに対して
ICM_CONFIGURE
メッセージを投げられれば良さそうです。ただし
m_mode != ICMODE_COMPRESS
より、m_mode
はICMODE_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.cppcase DRV_OPEN: return (LRESULT)CVCMCodec::Open((ICOPEN *)lParam2); case DRV_CLOSE: if (pCodec != NULL) delete pCodec; return TRUE;もう少し下を見ると
DRV_CLOSE
を受け取ったときに、pCodec
をdelete
しているところが見つかりました。さらにその上にDRV_OPEN
というそれっぽいのがあります。CVCMCodec::Open
を見てみましょう。utv_vcm/VCMCodec.cppCVCMCodec *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); }細かいところは読み飛ばすとして、まず
icmode = icopen->dwFlags;
が目に入ります。さっき言ってた変数ですね!さらにこの関数はreturn new CVCMCodec(fccHandler, icmode);
してます。確かにDRV_OPEN
とDRV_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.csusing 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
無事に呼び出せました。
余談: 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
にあった。
- 投稿日:2020-03-30T13:52:40+09:00
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; }