色選択ダイアログ(ColorDialog)の作成した色の保存

わんくまさんネタです。

ColorDialog クラスでは AllowFullOpen プロパティがデフォルトの True であれば、色の作成ボタンから基本色以外を選択すること、作成することができます。

image

ただし、何も実装しなければ、この ColorDialog クラスのインスタンスがなくなった時点で作成した色は失われます。

 

保存するためにはこの ColorDialog クラスの CustomColors プロパティから色を取り出して保存しておき、次回表示時に設定してあげる必要があります。

例:XmlSerializerやBinaryFormatterを使う

何も考えずに1ファイルにこの色情報だけを保存するのであれば、XmlSerializerやBinaryFormatterを使えばそれで終わります。

XmlSerializerの例

private void LoadCustomColors(string filePath, ColorDialog targetColorDialog)
{
    using (var stream = new FileStream(filePath, FileMode.Open))
    {
        var xmlSerializer = new XmlSerializer(typeof(int[]));
        targetColorDialog.CustomColors = (int[])xmlSerializer.Deserialize(stream);
    }
} private void SaveCustomColors(string filePath, ColorDialog sourceColorDialog) {
    using (var stream = new FileStream(filePath, FileMode.Create))
    {
        var xmlSerializer = new XmlSerializer(typeof(int[]));
        xmlSerializer.Serialize(stream, sourceColorDialog.CustomColors);
    }
}

BinaryFormatterの例

private void LoadCustomColors(string filePath, ColorDialog targetColorDialog)
{
    using (var stream = new FileStream(filePath, FileMode.Open))
    {
        var formatter = new BinaryFormatter();
        targetColorDialog.CustomColors = (int[])formatter.Deserialize(stream);
    }
} private void SaveCustomColors(string filePath, ColorDialog sourceColorDialog)
{
    using (var stream = new FileStream(filePath, FileMode.Create))
    {
        var formatter = new BinaryFormatter();
        formatter.Serialize(stream, sourceColorDialog.CustomColors);
    }
}

 

これらのサンプルメソッドの内、LoadCustomColors メソッドは Form の Load イベント(やコンストラクタ)、ダイアログの表示直前といったタイミングで呼び出してください。

LoadCustomColors(@"(ファイル名)", colorDialog1);

SaveCustomColors メソッドは Form の FormClosed イベントなどで呼び出してください。

SaveCustomColors(@"(ファイル名)", colorDialog1);

プロジェクトの設定(Properties.Settings)に保存したい

先の例はお手軽なものの、1ファイルに1つの情報だけを保存するのは微妙なので、もう一例紹介しておきます。

設定の構造にはデフォルトで int[] を扱える型がないので文字列で扱う例です。

あらかじめ、プロジェクトのプロパティの設定タブから ColorSettings という文字列型の設定値を「ユーザー」スコープで追加しておいてください。

image

以下は int 型配列を文字列に変えて、「,(カンマ)」区切りで連結しています。

private void LoadCustomColors(ColorDialog targetColorDialog)
{
    targetColorDialog.CustomColors =
        Properties.Settings.Default.ColorSettings.Split(new[] {','}, 16).Select(int.Parse).ToArray();
} private void SaveCustomColors(ColorDialog sourceColorDialog) {
    Properties.Settings.Default.ColorSettings = string.Join(",", sourceColorDialog.CustomColors);
    Properties.Settings.Default.Save();
}

こちらのサンプルメソッドは、LoadCustomColors メソッドを Form の Load イベントで呼び出す、SaveCustomColors メソッドを Form の FormClosed イベントで呼び出すといった想定です。

LoadCustomColors(colorDialog1);
SaveCustomColors(colorDialog1);

メソッドを分けずに、それらのイベントハンドラに直接中身を書く形でも良いでしょう。

カテゴリー: C#

Win10 1607(Anniversary Update)とD&D

Technet blogsにこんな記事が掲載されていた。

「資格情報ダイアログが表示されると、ドラッグが開始できなくなる問題について」
https://blogs.technet.microsoft.com/askcorejp/2016/09/14/cannot-drag-on-an-application-with-windows-10-anniversary-update/

 

この記事によると、Anniversary Update を当てた Windows 10、つまり Win10 Ver1607 において、資格情報ダイアログが表示されるようなことがあると、そのプロセスではドラッグが開始できなくなるそうだ。

手元で実験してみると、確かに、.NET の DoDragDrop メソッドを呼んでも何も起きなくなる
自前で MouseDown から MouseMove、MouseUp とドラッグ&ドロップを実装しているのであれば影響しないのかもしれないが、DoDragDrop メソッドを使っていればこの不具合に遭遇しうる。
そして、現状の回避策はプロセスの再起動しかない。

もっとも、資格情報が表示されるケースとなると、ユーザーが指定した任意のパスにアクセスするロジックを持つか、アプリが表示したコモンダイアログでユーザーがそういったパスを入力した時なので、発生現場は限られるのかもしれない。

(ファイラー機能を持つアプリは死亡率が上がりますが…)

unsigned short型配列からBitmapを作る

ありふれたネタかもしれませんが、コミュニティでそれらしい質問が上がっていたのでざっと書いてみました。

http://wp.me/a19AcI-5U
(Visual Studio 2010 / .NET 4 プロジェクト)

鍵となるのは Bitmap クラス、BitmapData クラスです。(WriteUInt16ToIndexed8Bitmap や WriteUInt16ToRgb24Bitmap あたり)
Bitmap クラスをサイズ・ピクセルフォーマットを指定してインスタンスを生成し、そのインスタンスの LockBits メソッドで書き込み先のメモリを用意します。
このメモリに必要な変換を書けたデータを 1 bytes / pixel や 3 bytes / pixel で Marshal.Copy や unsafe のポインタで書き込んで、UnlockBits メソッドで閉じるだけ。

ただし、Format8bppIndexed の場合はカラーパレットの設定も必要なのでお忘れなく。

※上記サンプルを .NET 3.5 以前で実行する場合、currentPointer 変数周りでコンパイルエラーになりますので、一旦 long 型にキャストして足し算してから、IntPtr 型にキャストし直してください。(currentPointer = (IntPtr)((long)currentPointer + stride); みたいな形)

カテゴリー: C#

Windows 7 API Code Pack を使ってサムネイルをとってみる

ちょっとした実験レベルでサムネイルを取り出すコードを書いてみた。Windows 7 API Code Pack を使うとかなりお手軽にできます。

最初に、Windows 7 API Code Pack を入手して参照する。NuGet なり、MSDN なりにて。
必須ではないので、自分で IShellFolder などの定義を用意できるのであれば、それでも代用可です。

KnownFolders クラスの static メンバーで Computer とか、Desktop とかがいるのでそこから列挙してみる。

foreach (ShellFolder item in KnownFolders.Computer) {

// とりあえず、ファイルシステムオブジェクトではなかったのでこの条件にした
// デバイス以外も列挙される恐れがあるかも?
if (!item.IsFileSystemObject)
{
     _devicesList.Add(item);
    listBoxDevices.Items.Add(item.Name);
}

}

この例では、ポータブルデバイスを列挙する方法が判然としていなかったので、ファイルシステムではないものだけをピックアップしている。


列挙したフォルダーをさらにたどるとか、ShellFileSystemFolder.FromFolderPath からお目当ての ShellFolder を得て、さらにそこから列挙するか、ShellFile.FromFilePath で一気にファイルをとるかしましょう。

ファイルに対応する ShellObject がとれればこっちのもの。以下のようなコードでサムネイルがとれる。

pictureBoxThumbnail.Image = shellFile.Thumbnail.Bitmap;

shellFile は対象の ShellObject だ。

iPhone を Windows 7 環境で USB 接続している状態で後述するサンプルを動かすとこんな感じでとれる。

image

実装したコードも 100 行ほどで済むので楽ちん。(ただ、Dispose とかきちんとしきれてないかも)

操作的には最初に左上のデバイスの列挙をクリックして右のリストボックスに表示されるフォルダーを選択して真ん中上のボタンを押してを繰り返して、JPEG が出てきたら選んで右のボタンを押すという形。

サンプル:http://sdrv.ms/PbBXIv

ここからファイル操作な話は未確認。Code Pack ベースではできないかもしれないなぁと思っていますが、さて…。

カテゴリー: C#

C++/CLIのIntelliSenseの今後

とりあえず、以前の記事にも書きましたが、Visual Studio 2010 Service Pack 1(VS2010SP1)ではC++/CLIのIntelliSenseは死んだままです。

さて、最近、Visual C++ Team BlogにC++/CLIのIntelliSenseの話題が出ていました。どうやら、次のメジャーバージョンアップで対応することで進めているようです。
2010ではなぜこうなったのかという背景を英文ですが、説明しているようですので、気になる方は読んでみてはいかがでしょうか。

C++/CLI IntelliSense in Visual Studio vNext
http://blogs.msdn.com/b/vcblog/archive/2011/03/03/10136696.aspx

要するにこんなところでしょうか?適当な解釈が含まれる可能性があるので、正確なところは原文を参照してください。

  • スケジュール的に無理だった。(仕事量を過小評価していた?)
  • C++/CLIだけ古い仕組みを動かそうとしてもやることが多すぎて無理と判断して諦めた(rejectした)
  • RTM後にすぐにC++/CLIのIntelliSenseの作業に取りかかったが、SP1に間に合わせるには準備時間が足りなかった。
カテゴリー: C#

Windows API Code Pack for .NET Frameworkを使ってコモンダイアログを出してみる

Windows 7で出てきた新要素を.NET Frameworkアプリケーションが使うためのクラスライブラリ、Windows API Code Pack for .NET Frameworkを使ってみました。

コモンダイアログってどんな感じかを軽く試してみてます。
Windows API Code Pack for .NET Frameworkの入手は以下のサイトから。

http://code.msdn.microsoft.com/WindowsAPICodePack

image

このテストアプリでは、KnownFolders.Librariesを指定してコモンダイアログを開き、物理パスとなんちゃって仮想パスを取ろうとしています。
ただ、仮想パスの取り方がエレガントじゃないので、もっとほかの方法がないもんかなー。

// Windows API Code Pack 1.1のサンプルを参考に書いたものです
// (source\Samples\Shell\CommonFileDialogsDemo)

// ライブラリの取得
ShellContainer librariesFolder = (ShellContainer)KnownFolders.Libraries;

// コモンダイアログの初期化
CommonOpenFileDialog dialog = new CommonOpenFileDialog();
dialog.AllowNonFileSystemItems = true;
// デフォルトフォルダの指定
dialog.DefaultDirectoryShellContainer = librariesFolder;

// ダイアログ表示
if (dialog.ShowDialog() != CommonFileDialogResult.Ok) return;

// 選択されたファイルの取得
// (ShellObjectでとれないことがある?サンプルではtry-catchしている)
ShellObject selectedObject = dialog.FileAsShellObject;
// 物理パスはParsingNameで得られる
absolutePathTextBox.Text = selectedObject.ParsingName;

// 仮想パスを得るきれいな手段が見当たらなかったので、親をたどってから
// 名前をつなげる方法を使ってみてます

// 親のアイテムをとれなくなるまで取ってみる
// Stackに積むことで後で根本から取り出す
Stack<ShellObject> pathStack = new Stack<ShellObject>();
ShellObject currentObject = selectedObject;
while (currentObject != null)
{
    pathStack.Push(currentObject);
    currentObject = currentObject.Parent;
}

// Stackを根本から取り出して、表示名をつなげることで仮想パスを組み立てる
string path = string.Empty;
while (0 < pathStack.Count)
{
    ShellObject currentItem = pathStack.Pop();
    path = Path.Combine(path, currentItem.Name);
}
// 仮想パス完成
// ただし、これだとデスクトップからになるので、親を探索する際にライブラリで打ち切るといったロジックがいるかも
// あと、C:\hogehoge.txtだと、ローカルディスクドライブ(C)みたいな文字列になるのがナンセンス
virtualPathTextBox.Text = path;

Code PackのMicrosoft.WindowsAPICodePack.dllとMicrosoft.WindowsAPICodePack.Shell.dllを同じフォルダに置けば、Visual Studio 2008で使えそうなアーカイブも置いておきます。

http://cid-9e1932af4be9e15d.office.live.com/self.aspx/.Public/Samples/20110105^_WinApiCodePackDialogTest.zip

 

追伸
実のところ、初期フォルダをがんばって指定しても、Windowsに覚えられちゃって、前回のフォルダで開くという挙動をするんですよね。
回避策があるかどうかは調べてません。

カテゴリー: C#

C++/CLIとIntelliSense – C++/CLIの冷遇はまだ続く

Visual Studio 2010ではC++/CLIのIntelliSenseが機能しないという制限があります。これはベータ2の段階から変わらず、フィードバックセンターにも以下のように登録されています。

■C++/CLIでもIntelliSenseが機能してほしい
https://connect.microsoft.com/VisualStudioJapan/feedback/details/519716/c-cli-intellisense

この状況はVisual Studio 2010 SP1 Betaでも改善されておらず、USのConnectを覗いてみました。

■Intellisense not working with VS2010 C++/CLI projects
https://connect.microsoft.com/VisualStudio/feedback/details/626825/intellisense-not-working-with-vs2010-c-cli-projects

ということで、SP1でもC++/CLIの冷遇は変わらないようです。
「将来のバージョンで」ってばっかりで、改善されないのはいかがなものか…。
これでは、私のようなC++/CLI層を書くことがある人からすると、Visual Studio 2010に移行するのは逆に苦労が待っているようにしか思えない…。

2011/03/05 C++/CLIのIntelliSenseの今後 : http://azulea.wordpress.com/2011/03/05/ccli%e3%81%aeintellisense/

Visual C++ 2008での参照設定の画面

Visual C#やVisual Basicと毛並みが異なるので、参考までにスクリーンショットを載せておきます。
この画面はプロジェクトのプロパティで表示でき、左側のノードで共通プロパティ→Framework と参照と辿ります。

C#でCOMを作る(とりあえず動くところまで)

★ご注意


CLR 2.0、CLR 1.xが同じプロセス空間にロードできない問題と互換性の問題があるため、マネージコードでCOMを作ることはお薦めしません。

 

0.はじめに

@ITでサンプルよこせーというようなニュアンスの発言を受け取ったのでのんびりと書いていきましょう。
環境はVisual Studio 2008 Professionalです。

 

1.プロジェクトを作る

「クラスライブラリ」を選択して、プロジェクトを作ります。
.NET Frameworkのバージョンの選択はお好みでどうぞ。

 

2.インターフェースを作る

プロジェクトメニュー等から新しい項目を追加でインターフェースを追加します。

 

3.インターフェースにCOMとして公開するために必要な属性をつける

ComVisible属性でCOMに公開するかどうかを設定でき、trueだと公開になります。
ClassInterface属性で遅延バインディングを認めるという意味でAutoDispatchにしています。必要に応じて変えて下さい。
最後のGuid属性でインターフェースに対してGUIDを設定しますが、このGUIDはツールでインターフェースやクラス毎に必ず違うものを生成して下さい。
[System.Runtime.InteropServices.ComVisible(true)]
[System.Runtime.InteropServices.InterfaceType(System.Runtime.InteropServices.ComInterfaceType.InterfaceIsDual)]
[System.Runtime.InteropServices.Guid("")]
public interface ITestClass
{
}
Visual Studio 2005/2008 Professionalであれば、ツールメニューに「GUIDの作成」があるので、
このツールで「4.Registry Format」で「Copy」ボタンを押して、クリップボードにコピーした文字列をGuid属性のところに入れます。
この際に、{ }の部分は外して下さい。
連続して生成する場合は、「New GUID」ボタンを押して下さい。
例:{D62189BE-C74D-46f5-8223-E0B8EC508BE5}が出てきたら、Guid属性には”D62189BE-C74D-46f5-8223-E0B8EC508BE5”を設定する。
 

 

4.インターフェースにメソッド・プロパティを追加

ひとまず、こんなところで、作ってみました。

[System.Runtime.InteropServices.ComVisible(true)]
[System.Runtime.InteropServices.InterfaceType(System.Runtime.InteropServices.ComInterfaceType.InterfaceIsDual)]
[System.Runtime.InteropServices.Guid("")]
interface ITestClass
{
    void HelloWorld();
    int Plus(int left, int right);
    int Number
    {
        get;
        set;
    }
}

 

5.インターフェースを実装するクラスを追加

毎度おなじみのプロジェクトの新しい項目の追加からクラスを作成します。

 

作成されたクラスで、インターフェースを実装するというように書きます。

public class Test : ITestClass
{
}
さらにクラス側にも色々と属性をつけておきます。
[System.Runtime.InteropServices.ComVisible(true)]
[System.Runtime.InteropServices.ClassInterface(System.Runtime.InteropServices.ClassInterfaceType.AutoDispatch)]
[System.Runtime.InteropServices.Guid("")]
public class Test : ITestClass

 

ここでITestClassと書いた部分にカーソルを合わせると下線が出てくるのでクリックして、メニューからインターフェースを実装しますを選択する。

そうすると、インターフェースを実装するスケルトンコードがずらずらと出てくるので中身を埋めていきます。
(プロパティのコードは何となく書いていますが、C#3.0あたりからはget;set;といった感じに省略できるはず)

public void HelloWorld()
{
    Console.WriteLine("Hello! World!");
}

public int Plus(int left, int right)
{
    return left + right;
}

private int number;

public int Number
{
    get { return this.number; }
    set { this.number = value; }
}

 

6.レジストリ登録するためにプロジェクトの設定を変えておく

順番が前後しましたが、プロジェクトの設定を変えておかないと、COMとして利用できる形に登録されません。
プロジェクトメニューから ~ のプロパティを選択します。
もしくはソリューションエクスプローラでプロジェクトを右クリックしてプロパティを選択しても構いません。

表示された画面から「ビルド」タブを選択して、下の方にある「COM相互運用機能の登録」にチェックを入れておきます。

この設定をしておくことで、ビルドしたときに自動的にレジストリに登録されるようになります。
(管理者権限が必要ですので、Vistaの場合はVisual Studioを管理者権限で起動する必要があるかもしれません。)

7.ビルドする

構文エラーとか特に問題がなければ、さっくりとビルドが成功するはずです。

 

8.使ってみる

Rubyは普段触りません。なので、検索で引っかかった情報を元に適当に組みました。

require 'win32ole'
instance = WIN32OLE.new('ComTestLibrary.Test')

instance.HelloWorld()

i = instance.Plus(1, 2)
print(i, "\n")

instance.Number = i
i = 25
print(instance.Number, "\n")
i += instance.Number
print(i, "\n")
instance.Number = 9
print(instance.Number, "\n")

このファイルをruby test.rbにような形で食わせると、次のように出力されます。

Hello! World!
3
3
28
9

ちゃんとCOMが呼び出せている様子です。
とりあえず、導入部分としては目的を達成できたかと思います。

 

9.留意点

・今回WIN32OLEモジュールを使っています。Dispatchなインターフェースがないとこの方法では使えないので属性の設定を無闇に変更しないようにしましょう。
・「とりあえず動いた」の状態ですので、これをスケルトンコードにしないようにしましょう。
・ビルドした環境以外で今回のDLLをレジストリに登録するには regasm コマンドを使用します。
・属性の意味はMSDNを見て調べましょう。

 

10.サンプルソース

http://cid-9e1932af4be9e15d.skydrive.live.com/embedrowdetail.aspx/.Public/ComTestLibrary.zip

※GUIDはそのまま使用しないようにしましょう。

カテゴリー: C#