バージョン情報と一口にいっても、実行ファイルのバージョン、DLLのバージョン、ClickOnceやインストーラのバージョンなど様々です。
バージョン情報ダイアログを作るために、これらのバージョンの取得方法を書いてみます。といってもコードで。
/// <summary>
/// ClickOnceで設定されたバージョンを取得
/// </summary>
public static Version ClickOnceVersion
{
if (!System.Deployment.Application.ApplicationDeployment.IsNetworkDeployed)
{
return null;
}
return System.Deployment.Application.ApplicationDeployment
.CurrentDeployment.CurrentVersion;
}
/// <summary>
/// 実行中のexeファイルのバージョンを取得
/// </summary>
public static string AssemblyVersion
{
return System.Windows.Forms.Application.ProductVersion;
}
/// <summary>
/// 実行中のexeファイルと同一ディレクトリにあるDLLの情報を取得
/// </summary>
public static List<System.Diagnostics.FileVersionInfo> DllVersion
{
var versions = new List<System.Diagnostics.FileVersionInfo>();
//自身のファイルパスを取得し、同一ディレクトリのdllファイルを一覧する
string path = System.IO.Path.GetDirectoryName(
System.Windows.Forms.Application.ExecutablePath);
foreach (string name in System.IO.Directory.GetFiles(path, "*.dll"))
{
System.Diagnostics.FileVersionInfo info =
System.Diagnostics.FileVersionInfo.GetVersionInfo(name);
versions.Add(info);
}
return versions;
}
このような感じで、バージョン情報ダイアログが作れそうです。
※DLLのバージョン取得はもう少し工夫しないと問題がありそうですが・・・
C#等のアプリで、自身がClickOnceでインストール・実行されているかをチェックするには、
System.Deployment.Application.ApplicationDeployment.IsNetworkDeployed
を調べます。
しかしこのプロパティ、普通に起動するとfalseが取得できますが、Visual Studioから起動するとなぜかフリーズすることがあります。ありました。
この場合は、ClickOnceアプリの実体が保存される C:/Users/ユーザ名/AppData/Local/Apps/2.0 を削除して、コンピュータを再起動すると直るようです。直りました。
起動プロセスが複雑になるとたまに面倒ですね。
先日のiPhone UDP受信テストの際に使った、WindowsからUDPパケットを送りつけるプログラムを載せてみます。
一切の遠慮無く、1秒に20枚のペースで、デスクトップに置いてあるimg0.png~img9.pngの画像ファイルを一方的に送りつけます。
///一定時間ごとにUDPで画像を一方的に送りつける
private void SendData()
{
//ローカルポート番号2222にバインドする
System.Net.Sockets.UdpClient udp = new System.Net.Sockets.UdpClient(2222);
int count = 0; //何番目の画像を送信するかのカウンタ
Timer timer = new Timer();
timer.Tick += new EventHandler((s, e) =>
{
if (++count > 9)
{
count = 0;
}
//デスクトップのimg0.png~img9.pngを送る
string filename = @"C:\Users\baba\desktop\img\" + count + ".png";
using (System.IO.FileStream fs = new System.IO.FileStream(filename, System.IO.FileMode.Open, System.IO.FileAccess.Read))
{
byte[] sendBytes = new byte[fs.Length];
fs.Read(sendBytes, 0, sendBytes.Length);
//宛先IPとポート番号を指定する
udp.Send(sendBytes, sendBytes.Length, "192.168.100.50", 5555);
}
});
timer.Interval = 50;
timer.Start();
}
.NETは簡単にこういったコードが書けて便利ですね。
何の工夫も最適化も無いですが、50枚/秒くらいなら全然問題にならない程度の速度は出ていました。
C#でHTTPサーバを作るのはすごく簡単、
HttpListener listener = new HttpListener();
listener.Prefixes.Add("http://*:8000/"); //ポート番号は好みで。80でもOK
listener.Start();
while (true)
{
HttpListenerResponse response = listener.GetContext().Response;
response.OutputStream.Write(data, 0, (int)data.Length); //ここで何かdataを出力する
response.Close();
}
のようなコードだけで出来てしまいます。
しかし、これだとWindows Vista / 7 のUAC有効環境で、HttpListenerExceptionが発生します。
Windowsファイアウォールを無効にしても結果は変わりません。
UAC有効の状態では、通常権限のプログラムは、localhost以外からの接続を受け付けられないようです。
そもそもテストやミニプログラム以外でHttpListenerを使うか疑問が大きいので、手抜きで安直な解決方法を探ってみます。
localhostに限定
ローカルでテストする分には、
listener.Prefixes.Add(”http://localhost:8000″);
のようにすればとりあえず動きます。
管理者権限にする
localhost以外からも接続させたいなら、管理者権限で動かしてしまうのが簡単です。
Visual Studioを起動する際に管理者権限にしておけば、デバッグで起動するプログラムも管理者権限になるので、例外は発生しなくなります。
WPFで枠を消す の続きです。
前回紹介した方法は、以下のような内容でした。
- WindowStyle=None, AllowsTransparency=True にすると枠が消える
- 移動できるように、WM_NCLBUTTONDOWN + HTCAPTION を送信する
しかし、この方法には欠点があります。
AllowsTransparencyを有効にしていると、WindowsFormsHostが表示されなくなってしまうのです。
http://msdn.microsoft.com/ja-jp/library/aa970911(VS.80).aspx
HwndHost クラスは、レイヤ表示をサポートしません。つまり、WindowsFormsHost 要素で Opacity プロパティを設定しても何の効果もなく、AllowsTransparency が true に設定されている他の WPF ウィンドウとのブレンド操作は行われません。
そこで、DirectX と連携する際などは、AllowsTransparency を False のままにしておく必要があります。
困ったときのWinAPI。直接ウィンドウスタイルを設定して、枠を消してしまいましょう。
Windows Forms Application 時代は、CreateParams で設定していたような内容ですね。
[DllImport("user32.dll")]
static extern long GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll")]
static extern long SetWindowLong(IntPtr hWnd, int nIndex, long dwNewLong);
[DllImport("user32.dll")]
static extern UInt32 SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, int wFlags);
private void RemoveFrame()
{
long WS_THICKFRAME = 0x00040000L;
int GWL_STYLE = -16;
int SWP_FRAMECHANGED = 0x0020;
int SWP_NOSIZE = 0x1;
int SWP_NOMOVE = 0x2;
int SWP_NOZORDER = 0x4;
IntPtr hwnd = new System.Windows.Interop.WindowInteropHelper(window).Handle;
SetWindowLong(hwnd, GWL_STYLE, GetWindowLong(hwnd, GWL_STYLE) ^ WS_THICKFRAME);
SetWindowPos(hwnd, IntPtr.Zero, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER);
}
やっていることは以下のような感じです。
- GetWindowLongでWindowStyleを取得
- SetWindowLongで、今までのスタイルからWS_THICKFRAME(ウィンドウ枠)を除いたものを設定
- SetWindowPosで表示を更新
最後のSetWindowPostをやらないと、サイズ変更するまで描画が崩れてしまいます。
注意点としては、WS_THICKFRAME を外すと、WM_NCLBUTTONDOWN で HTBOTTOMRIGHT を送信しても、サイズ変更できなくなります。
諦めて、自分でサイズ変更処理を書くしかないみたいですね。
Windowsのデフォルトでは、省エネのためしばらく操作しないとディスプレイ出力が停止します。
マウス操作などすれば再度ディスプレイが付きますが、常駐アプリなどを作成する場合、プログラムからディスプレイをONにしたい場合があります。
そんなときは、PostMessageでWM_SYSCOMMANDメッセージを送り、wParamにSC_MONITORPOWER、lParamに-1をセットすればOKです【方法A】。
http://msdn.microsoft.com/en-us/library/ms646360(VS.85).aspx
しかしこの方法では、WindowsXPなどの環境で、一瞬ディスプレイが付いたあと、すぐに再度OFFになってしまう場合があります。
これについて解析している方もいらっしゃいましたが、ここでは別の方法として、「マウスを動かす」ことによって省電力モードを解除してみましょう【方法B】。
方法AとBの両方のソースをご紹介します。C#でWPFな環境を想定しています。
なお、方法Aでは引数を変えるだけでディスプレイ電源OFFも可能です。
const int HWND_BROADCAST = 0xffff;
const int WM_SYSCOMMAND = 0x0112;
const int SC_MONITORPOWER = 0xF170;
const int INPUT_MOUSE = 0;
const int INPUT_KEYBOARD = 1;
const int MOUSEEVENTF_MOVED = 0x0001;
[StructLayout(LayoutKind.Sequential)]
public struct MOUSEINPUT
{
public int dx;
public int dy;
public int mouseData;
public int dwFlags;
public int time;
public IntPtr dwExtraInfo;
}
[StructLayout(LayoutKind.Sequential)]
public struct INPUT
{
public int type;
public MOUSEINPUT mi;
}
[DllImport("user32.dll")]
public static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
public static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);
//【方法A】
void WakeupDisplayA()
{
//最後の引数は、1=省電力モード、2=ディスプレイ停止、-1=省電力モードから復帰
PostMessage((IntPtr)HWND_BROADCAST, (IntPtr)WM_SYSCOMMAND, (IntPtr)SC_MONITORPOWER, (IntPtr)(-1));
}
//【方法B】
void WakeupDisplayB()
{
//適当に動かす
INPUT[] inputs = new INPUT[2];
inputs[0].mi.dx = 10;
inputs[0].mi.dwFlags = MOUSEEVENTF_MOVED;
inputs[1].mi.dx = -10;
inputs[1].mi.dwFlags = MOUSEEVENTF_MOVED;
SendInput(2, inputs, System.Runtime.InteropServices.Marshal.SizeOf(inputs[0]));
}
最近はWPFに触れる機会が増えてきたので、小ネタを少しずつ書いていきます。
今日は、ウィンドウ枠を消す方法です。デザインを格好良くするために、ウィンドウ枠を消したいことも多々ありますよね。
Window.xaml で、
- WindowStyle を None にする
- AllowTransparency を true にする
以上で枠が消えます。
・・・これだけだとつまらないので、枠が消えても移動・リサイズが正しくできるようにしてみます。
なお、システムメニューを表示する方法は先日の記事をご覧下さい。
まず、右下の方に適当なリサイズハンドルを用意します。ButtonやImageなどで良いでしょう。
そして、MouseLeftButtonDown イベントに以下のイベントハンドラを追加します。
[DllImport("user32.dll")]
public static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
private void Hoge_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
//WM_NCLBUTTONDOWN=0xA1, HTBOTTOMRIGHT=17
IntPtr hwnd = new System.Windows.Interop.WindowInteropHelper(this).Handle;
PostMessage(hwnd, 0xA1, (IntPtr)17, IntPtr.Zero);
}
これで、そのハンドルをドラッグするとリサイズできるようになります。
また、17(HTBOTTOMRIGHT)のところを 2(HTCAPTION)に変えると、ウィンドウを移動できるようになります。
WPFになっても、WinAPIの知識は役立つものです。
いろいろな本を読むと言っても、エンジニアとして専門分野を強化していくことをサボっては元も子もありません。
雑誌や技術書のインプットは減らさず、総量を増やしていくように心がけています。
最近はWeb以外の案件も少しずつ増えてきて、今後のWindows開発でメインとなるC#力を強化しようと思って本書を購入しました。
C#3.0以降はきちんと仕様を追いかけていなかったのですが、バージョンアップの素晴らしさに感動を覚えました。
よく見る表面的な「新機能LINQ!」みたいな記事では分からない、設計思想と数々の機能の連携による解説で、C#3.0の魅力が十二分に伝わってきます。
もともとC#は大好きな言語ですが、本書を読んでさらに大好きになりました。早速、C#2.0で書いていて不満があった部分を3.0で書き直してみたり、色々実験中です。
C#プログラマーやJavaからC#へ移行する方に、自信を持っておすすめできる良書です。
4.0ではdynamic型と名前付オプション引数で、さらにすごいことになるみたいですね。
引き続きWPFの小ネタです。
WPFウィンドウのアイコンは、System.Windows.Media.ImageSource型です。
プロジェクトのリソースにアイコンを指定すると(プロジェクト右クリック→プロパティ→リソースから追加)、System.Drawing.Icon型になります。
これらは直接変換できないようですが、以下のようにStreamを介せば変換できます。ただ、もっとスマートな方法が欲しいですね・・・
そもそも、WPFではアイコンをこのように指定することが希なのかも知れません。
System.IO.MemoryStream s = new System.IO.MemoryStream();
(Properties.Resources.myicon as System.Drawing.Icon).Save(s);
window.Icon = System.Windows.Media.Imaging.BitmapFrame.Create(s);
WPFはWindowsFormsに比べ、デザインを柔軟に設定できて良いですね。
また、デザインとロジックの分離がとてもやりやすいです。Visual Studio 2008のエディタがこなれていないのと、Bindingマークアップ拡張が分かりにくいのが普及を妨げている感がありますが(?)、おすすめです。
さて、大した内容では無いのですが、WPFで任意のタイミングでシステムメニューを表示させる方法をご紹介します。
WPFでと言いながらWinAPIのお話で、かなり有名な内容ですが、まあ一応。

システムメニュー
[DllImport("user32.dll")]
public static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
private void Button_Click(object sender, MouseButtonEventArgs e)
{
//マウス座標を取得し、lParamに加工
int x = System.Windows.Forms.Control.MousePosition.X;
int y = System.Windows.Forms.Control.MousePosition.Y;
IntPtr lParam = new IntPtr(x | y << 16);
//Windowハンドルを取得し、0x313メッセージを送信
IntPtr hwnd = new System.Windows.Interop.WindowInteropHelper(this).Handle;
PostMessage(hwnd, 0x313, IntPtr.Zero, lParam);
}
要するに0×313を送るだけです。これはWinUser.hにも書かれていない隠し定数ですが、いろんなところで紹介されて逆に有名な値です。0×313。