0.Intro
テレワークが一般的になって久しいです。その為に物理的な作業を極力なくそうという動きがあちらこちらで起こりましたが、このような世の中になってもまだハンコが幅を利かせています。そういう時に役に立つのが電子印鑑です。法的な効力は無いにせよ見た目的にもよいので大活躍しました。僕の会社では総務部より「クリップ○タンプ」という著名なソフトが推奨されていましたが、社内に導入しているウィルス対策ソフトにひっかかるようになりました。なので代替品を探したのですが「こんなのでいいんだよ」的なものが意外とあるようで無いです。ですので自作する方向で進めました(採用されるかどうかは別して)。
1.出来上がり
言語:C#
プロジェクトのテンプレート:Windowsフォームアプリケーション(.NET Framework)(素のWin10でも動くように.NET Framework 4.7.2)
本当にはWPFで作りたかったのですがイベント周りが厄介そうだったのでWinFormに。
拘った点は以前に導入されていたソフトがドラッグアンドドロップで貼り付けられるようになっていたので同等の機能を実現したかったのですが、こちらが一番厄介でした。結果的に何とか実現できましたので、本稿はその結果の紹介記事みたいなものです。
2.ライブラリ
肝心の印鑑部分はこちらのライブラリを使わせていただきました。
https://github.com/try0/StampImages
Nugetでも導入できますが、少し変更したかったのでGithubから落とさせていただきプロジェクト参照しました。
必要なプロジェクトは以下です。
StampImages.Core.csproj
StampImages.Core.Drawing.Common.csproj
StampImages.Core.SkiaSharp.csproj
まことにありがとうございました。
3.ポイント
1.印鑑部分
大変素晴らしい作りでしたので導入において特に困るところは無かったです。
「日付印」のみで良かったので選択肢は必要無し。以下のコードだけで画像として取得できます。
StampImageFactory stampImageFactory = new StampImageFactory(new StampImageFactoryConfig());
var stamp = new ThreeAreaCircularStamp
{
TopText = new StampText { Value = textBox1.Text, Size = 22 }, //部署名
MiddleText = new StampText { Value = dateTimePicker1.Value.ToString("yyyy.MM.dd"), Size = 30 }, //日付
BottomText = new StampText { Value = textBox2.Text, Size = 25 } //名前
};
var bitmap = stampImageFactory.Create(stamp);
pictureBox1.Image = bitmap;
貼り付けたいときはビットマップをクリップボードに入れるだけで貼り付けられるようになります。
2.ドラッグアンドドロップ
試行錯誤した結果実現できました。ポイントを記載します。
1.左クリックを放すタイミングを取得
色々調べた結果以下のような方法となりました。
[DllImport("user32.dll")]
public static extern short GetAsyncKeyState(System.Windows.Forms.Keys vKey);
private void timer1_Tick(object sender, EventArgs e)
{
if (GetAsyncKeyState(Keys.LButton) == 0)
{
if (flgClick == true)
{
if (!GetWindowContainState(this))
{
flgClick = false;
//ここでハンコの貼り付け等実処理を行う
…
…
…
}
else
{
flgClick = false;
}
}
}
}
タイマーでチェックし、0になった時がリリースされた時となります。フラグで制御しているは、今回必要なリリースは「ハンコの上でクリックされ且つフォーム外でリリースされた」もののみです。ですのでハンコの上でクリックされたときにフラグを立て、フォーム内の時、または貼り付けが終わった後にフラグを戻すということを行っています。
2.左クリックが放された位置を取得
ポイントはファーム外でのリリースにしか必要ないというところです。「1.左クリックを放すタイミングを取得」でタイマーを導入していますが、フォーム外の位置を取得するにはタイマーが必要だそうです。
タイマーを使っていれば以下で位置がとれます。
System.Windows.Forms.Cursor.Position.X
System.Windows.Forms.Cursor.Position.Y
フォーム外かどうかの判定は以下の記事を参考にしました。
https://atmarkit.itmedia.co.jp/fdotnet/dotnettips/382ctrlcontain/ctrlcontain.html
3.貼り付けたいアプリを取得
方法としては当該アプリの一つ後ろのウィンドウをアクティブにするという指定の仕方をしました。
以下のサイトコードを参考に
https://okwave.jp/qa/q2653131.html
C#に置き換えました。置き換えたコードは以下です
public static extern IntPtr GetWindow(IntPtr hwnd, GetWindowType uCmd);
[System.Runtime.InteropServices.DllImport("user32.dll")]
public static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int GetWindowTextLength(IntPtr hWnd);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
[System.Runtime.InteropServices.DllImport("user32.dll")]
public static extern bool IsWindowVisible(IntPtr hWnd);
[System.Runtime.InteropServices.DllImport("user32.dll")]
public static extern IntPtr GetLastActivePopup(IntPtr hWnd);
public delegate bool EnumWindowsDelegate(IntPtr hWnd, IntPtr lparam);
[System.Runtime.InteropServices.DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool EnumWindows(EnumWindowsDelegate lpEnumFunc, IntPtr lParam);
[System.Runtime.InteropServices.DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SetForegroundWindow(IntPtr hWnd);
public enum GetWindowType : uint
{
First = 0,
Last = 1,
Next = 2,
Prev = 3,
Owner = 4,
Child = 5,
EnabledPopup = 6
}
public static IntPtr glngOwnerHwnd;
public static IntPtr glngPrevHwnd;
public static bool gblnFound;
private static bool EnumWindowCallBack(IntPtr hWnd, IntPtr lparam)
{
bool rec = false;
if (gblnFound == true)
{
if ((IsWindowVisible(hWnd) == true) &&
(GetWindowTextLength(hWnd) > 0) &&
(GetWindow(hWnd, GetWindowType.Owner) == IntPtr.Zero)) {
glngPrevHwnd = GetLastActivePopup(hWnd);
rec = false;
}
else
{
rec = true;
}
}else{
//ウィンドウハンドルを保存
if (glngOwnerHwnd == hWnd)
{
gblnFound = true;
}
rec = true;
}
return rec;
}
glngOwnerHwnd = this.Handle;
glngPrevHwnd = IntPtr.Zero;
gblnFound = false;
EnumWindows(new EnumWindowsDelegate(EnumWindowCallBack), IntPtr.Zero);
int textLen = GetWindowTextLength(glngPrevHwnd);
if (0 < textLen)
{
SetForegroundWindow(glngPrevHwnd);
}
4.貼り付ける位置を定める
一番多い使われ方であるExcelに貼り付けられるかでテストしていたのですがなかなか思うようにいかず。。。
試行錯誤の結果上記の「3.貼り付けたいアプリを取得」の次にそのアプリをクリック、で実現できました。正確には左クリックをリリースした位置をクリックすることになるのでセルが選択されます。そしてその位置にコピーすることになるので結果、目的を達することが出来ます。
コード的には以下です
[DllImport("USER32.dll", CallingConvention = CallingConvention.StdCall)]
static extern void mouse_event(int dwFlags, int dx, int dy, int cButtons, int dwExtraInfo);
private const int MOUSEEVENTF_LEFTDOWN = 0x2;
private const int MOUSEEVENTF_LEFTUP = 0x4;
// マウスの左ボタンダウンイベントを発生させる
mouse_event(MOUSEEVENTF_LEFTDOWN, System.Windows.Forms.Cursor.Position.X, System.Windows.Forms.Cursor.Position.Y, 0, 0);
// マウスの左ボタンアップイベントを発生させる
mouse_event(MOUSEEVENTF_LEFTUP, System.Windows.Forms.Cursor.Position.X, System.Windows.Forms.Cursor.Position.Y, 0, 0);
5.コピーする
「コピーする」という命令はないので(受け取り側で実装される行為であるため(だと思います))方法としては「Ctrl+v」をアプリ側で発行する形になります。
SendKeys.SendWait("^v");
WinFormはこれでいいのですが、WPFだとかなり厄介のようです。その厄介な動作をまとめてくれた以下のソースが素晴らしいです。マウスのシュミレーションも行ってくれています。
4.一応完成して
残課題として、サイズを自由に選べる機能は欲しいかなと思います。
後「発行元を確認できませんでした。このソフトウェアを実行しますか? 」が出てくるのが厄介ですね。テスト証明で署名したり色々してみたのですが…さすがに証明書のために自腹はきついです。初回だけのようですのでこれ採用されたら「大目に見てください」で通しますw