2016年2月7日日曜日

XPSファイルをページ単位で分割

かなり久しぶりの投稿ですw

仕事でDocumentViewerにExcelの印刷プレビューを表示する必要ができて、どうしようかなぁと考えて見たんだけど、一旦XPSに出力してから読み込めばいいやって結論になっったんで、ちょこちょこっと作ってみました。

まずはエクセルをXPSに変換するところ
        private void ConvertExcelToXps(string excelFile, string xpsFile)
        {
            // COMオブジェクト宣言とEXCEL読み込み
            Excel.Application app = new Excel.Application();
            Excel.Workbooks books = app.Workbooks;
            Excel.Workbook book = books.Open(excelFile);
            // Excel → XPS 変換
            book.ExportAsFixedFormat(Excel.XlFixedFormatType.xlTypeXPS, xpsFile);
            // COM解放
            book.Close();
            Marshal.ReleaseComObject(book);
            book = null;
            books.Close();
            Marshal.ReleaseComObject(books);
            books = null;
            app.Quit();
            Marshal.ReleaseComObject(app);
            app = null;
        }
(どうでもいいけど、COM操作ってホントめんどくさい。。)

で、変換したXPSを読み込んでDocumentViewerに設定
            using (var doc = new XpsDocument(xpsFile, FileAccess.Read))
            {
                this.documentViewer.Document = doc.GetFixedDocumentSequence();
            }

完成!! 簡単、簡単♪♪

と思っていたら、

「ページ送りのボタンをつけてよ」
「はい? マウスホイールでスクロールできますよ」
「いや、前頁ボタンとか次頁ボタンでページを切り替えたいの」
「なんで?」
「だって、客がそういうから。。。」
「だから何で、客はそう言うの?」
「知らん。仕様なんだからやれよ」
「・・・・」

というわけで、DocumentViewerには1ページずつ表示することになりました。。。
でも、どうやって?

DocumentViewerにはそれっぽいプロパティはなさそうだし、どうしようかなと思っていたらさっきのヤツが

「1シートずつXPSにすればいいんじゃないの?」
「1シートが2ページ以上になることはないんですか?」
「・・・・あるね。」
「フッターにページ番号ついてませんでしたっけ?あれはどうするんですか?」
「・・・・ダメだね。」


結局、「Excel →(変換)→ XPS →(分割)→ ページ毎のXPS 」という流れでXPSファイルを作ることにしました。
が、これがわからなくて結構めんどくさかった。

最終的にはこんな感じで XPSをページ単位に分割できました。
        private int SeparateXps(string sourceXpsFile, string destDirectory)
        {
            int count = 0;
            // 分割後のファイルパスのフォーマット
            string fileFormat = destDirectory + Path.GetFileNameWithoutExtension(sourceXpsFile) + "{0}" + Path.GetExtension(sourceXpsFile);
            using (var source = new XpsDocument(sourceXpsFile, FileAccess.Read))
            {
                // 元のXPSファイルを読み込む
                var doc = source.GetFixedDocumentSequence();
                // ページ数を取得(戻り値用)
                count = doc.DocumentPaginator.PageCount;
                // 元のXPSファイルを1ページずつ処理
                for (int i = 0; i < count; i++)
                {
                    using (var destination = new XpsDocument(string.Format(fileFormat, i + 1), FileAccess.Write))
                    {
                        // 1ページずつに分割したXPSファイルを作成
                        var writer = XpsDocument.CreateXpsDocumentWriter(destination);
                        // 作成した1ページ分のXPSファイルに元のXPSファイルからVisualをコピー
                        writer.Write(doc.DocumentPaginator.GetPage(i).Visual);
                    }
                }
            }
            return count;
        }

作ったXPSを全部読み込んでList<FixedDocumentSequence>に保持。
前頁、次頁ボタンでDocumentViewerのDocumentを切り替えてやることで何とか行けそう。

でも、これだとページ数が多いとけっこう重くなりそうな気がする。

まぁ、いいや。
その時は、またその時考えようw

2013年11月23日土曜日

ボタンクリックでメニューを表示してみる

Widowsフォーム アプリケーションでボタンをクリックした時にメニューを表示してみる。
といっても、ただメニューを表示するだけなら簡単!!
    public partial class CustomButton : Button
    {
        // ボタンクリックで表示するメニュー
        public ContextMenuStrip DropDownMenu { get; set; }

        // コンストラクタ
        public CustomButton()
        {
            InitializeComponent();
        }

        protected override void OnClick(EventArgs e)
        {
            // メニューを表示可能か?
            if (DropDownMenu != null)
            {
                // メニューを開く
                DropDownMenu.Show(this, new Point(0, Height));
            else
            {
                // クリックイベントを発生させる
                base.OnClick(e);
            }
        }
    }
namespaceは省略してます。
で、これだけでとりあえずDropDownMenuプロパティに入れたメニューがボタンクリックで開くようになる。こんな感じ。
まあ、これだけでも概ねOKだけどメニューが開いている状態でもう一度ボタンをクリックすると、メニューが一瞬閉じてからすぐにまた開いてしまう。
メニューを開いている状態でボタンクリックしたら閉じて欲しいんだけど。。
ボタンクリックの前にメニューが閉じてしまうから「メニューが閉じられていたら開く」ってこともできない。
        protected override void OnClick(EventArgs e)
        {
            // メニューを表示可能か?
            if (DropDownMenu != null && !DropDownMenu.Visible) // ←すでにメニューは閉じているのでダメ!
            {
                // メニューを開く
                DropDownMenu.Show(this, new Point(0, Height));
            }
            else
            {
                // クリックイベントを発生させる
                base.OnClick(e);
            }
        }
しょうがないのでWndProcで何とかならないかと調べてみると、ボタンクリック時にこんなメッセージが飛んできているのを発見!

「WM_MOUSEACTIVATE = 0x0021(マウスクリックによりアクティブウインドウが移った)」

つまり、メニューが閉じられてアクティブなウィンドウがメニューからフォームに移ったって意味らしい。(ちなみにメニューが開く時はフォームからメニューにアクティブウィンドウが移ったって意味になる)

しかもこのメッセージ、メニューが閉じる前に飛んできてるやん!!

というわけで、こんな感じに実装してみた。
    public partial class CustomButton : Button
    {
        // WndProcのWM_MOUSEACTIVATE(マウスクリックによりアクティブウインドウが移った)のメッセージID
        private const int WM_MOUSEACTIVATE = 0x0021;

        // メニューのClosedイベント
        private ToolStripDropDownClosedEventHandler closedHandler;

        // Click時にメニューを表示可能であることを示すフラグ
        private bool allowShowMenu = true;

        // メニューがボタンクリックによって閉じられることを示すフラグ
        private bool menuCloseByClick = false;

        // ボタンクリックで表示するメニュー
        public ContextMenuStrip DropDownMenu { get; set; }

        // コンストラクタ
        public CustomButton()
        {
            InitializeComponent();
            // メニューのClosedイベントハンドラ生成
            closedHandler = new ToolStripDropDownClosedEventHandler(MenuClosed);
        }

        protected override void OnClick(EventArgs e)
        {
            // メニューを表示可能か?
            if (DropDownMenu != null && allowShowMenu)
            {
                // イベントハンドラ登録
                DropDownMenu.Closed += closedHandler;
                // メニューを開く
                DropDownMenu.Show(this, new Point(0, Height));
            }
            else
            {
                // クリックイベントを発生させる
                base.OnClick(e);
            }
            // 次回クリックに備えてメニュー表示可能フラグを立てる
            allowShowMenu = true;
            // 次回クリックに備えてメニューがボタンクリックによって閉じられたことを示すフラグをたたむ
            menuCloseByClick = false;
        }

        // メニューが閉じられた時の処理
        private void MenuClosed(object sender, ToolStripDropDownClosedEventArgs e)
        {
            // イベントハンドラを破棄
            DropDownMenu.Closed -= closedHandler;
            if (e.CloseReason == ToolStripDropDownCloseReason.AppClicked && menuCloseByClick)
            {
                // ボタンクリックによってメニューが閉じられた場合
                // この後に走るOnClickでメニューを開かないようにフラグを立てる
                allowShowMenu = false;
            }
            else
            {
                // ボタンクリック以外でメニューが閉じられた場合
                // 次回のボタンクリックでメニューが開くようにフラグをたたむ
                allowShowMenu = true;
            }
            // ボタンクリック以外でメニューが閉じられた場合に備えて
            // ここでボタンクリックによってメニューが閉じられたことを示すフラグをたたむ
            menuCloseByClick = false;
        }

        // Windowメッセージを処理します
        protected override void WndProc(ref Message m)
        {
            if (m.Msg == WM_MOUSEACTIVATE)
            {
                // ボタンクリックによってメニューが閉じられてアクティブウィンドウが移った場合フラグを立てる
                // (正確にはメニューが表示される時にもフラグが立つがMenuClosedは走らないので問題ない)
                menuCloseByClick = true;
            }
            base.WndProc(ref m);
        }
    }
ちゃんとボタンクリックでメニューが開いて、もう一回クリックで閉じてる。いい感じかな(^_^)
やっていることは簡単!
1.WndProcでmenuCloseByClickフラグを立てる
2.メニューClosedイベントでmenuCloseByClickが立っていたらallowShowMenuフラグを立てる
3.ボタンクリックでallowShowMenuが立っていたらメニューを開いて、フラグを戻しておく
特に問題なさそうだし、これでOKかな。。。

あれ?ボタンを右クリックしてメニューを閉じたらどうなるんだろう???
・・・ボタンを2回クリックしないとメニューが開かなくなった。。○| ̄|_
ボタンをダブルクリックしてメニューを開くとメニューClosedイベントが2回走るし。。。

なんか、もう面倒臭くなってきたなぁ( ̄◇ ̄)
    public partial class CustomButton : Button
    {
        // WndProcのWM_MOUSEACTIVATE(マウスクリックによりアクティブウインドウが移った)のメッセージID
        private const int WM_MOUSEACTIVATE = 0x0021;

        // メニューのClosedイベント
        private ToolStripDropDownClosedEventHandler closedHandler;

        // Click時にメニューを表示可能であることを示すフラグ
        private bool allowShowMenu = true;

        // メニューがボタンクリックによって閉じられることを示すフラグ
        private bool menuCloseByClick = false;

        // ボタンクリックで表示するメニュー
        public ContextMenuStrip DropDownMenu { get; set; }

        // コンストラクタ
        public CustomButton()
        {
            InitializeComponent();
            // メニューのClosedイベントハンドラ生成
            closedHandler = new ToolStripDropDownClosedEventHandler(MenuClosed);
        }

        // マウス押下時の処理
        protected override void OnMouseDown(MouseEventArgs mevent)
        {
            base.OnMouseDown(mevent);
            // ボタン上で左クリック以外の場合、次回の左クリックで
            // メニューが開くようにフラグを立てる
            if (mevent.Button != MouseButtons.Left)
            {
                allowShowMenu = true;
            }
        }

        // ボタンクリック時の処理
        protected override void OnClick(EventArgs e)
        {
            // メニューを表示可能か?
            if (DropDownMenu != null && allowShowMenu)
            {
                if (!DropDownMenu.Visible)  // メニューが既に開いている場合は何もしない(ダブルクリック対策)
                {
                    // イベントハンドラ登録
                    DropDownMenu.Closed += closedHandler;
                    // メニューを開く
                    DropDownMenu.Show(this, new Point(0, Height));
                }
            }
            else
            {
                // クリックイベントを発生させる
                base.OnClick(e);
            }
            // 次回クリックに備えてメニュー表示可能フラグを立てる
            allowShowMenu = true;
            // 次回クリックに備えてメニューがボタンクリックによって閉じられたことを示すフラグをたたむ
            menuCloseByClick = false;
        }

        // メニューが閉じられた時の処理
        private void MenuClosed(object sender, ToolStripDropDownClosedEventArgs e)
        {
            // イベントハンドラを破棄
            DropDownMenu.Closed -= closedHandler;
            if (e.CloseReason == ToolStripDropDownCloseReason.AppClicked && menuCloseByClick)
            {
                // ボタンクリックによってメニューが閉じられた場合
                // この後に走るOnClickでメニューを開かないようにフラグを立てる
                allowShowMenu = false;
            }
            else
            {
                // ボタンクリック以外でメニューが閉じられた場合
                // 次回のボタンクリックでメニューが開くようにフラグをたたむ
                allowShowMenu = true;
            }
            // ボタンクリック以外でメニューが閉じられた場合に備えて
            // ここでボタンクリックによってメニューが閉じられたことを示すフラグをたたむ
            menuCloseByClick = false;
        }

        // Windowメッセージを処理します
        protected override void WndProc(ref Message m)
        {
            if (m.Msg == WM_MOUSEACTIVATE)
            {
                // ボタンクリックによってメニューが閉じられてアクティブウィンドウが移った場合フラグを立てる
                // (正確にはメニューが表示される時にもフラグが立つがMenuClosedは走らないので問題ない)
                menuCloseByClick = true;
            }
            base.WndProc(ref m);
        }

        // ボタン描画時の処理
        protected override void OnPaint(PaintEventArgs pevent)
        {
            base.OnPaint(pevent);
            // 三角形を描画
            using (var brush = new SolidBrush(ForeColor))
            {
                float width = 7;
                float height = 5;
                float x = pevent.ClipRectangle.Width - width - 5;
                float y = (pevent.ClipRectangle.Height - height) / 2;
                var triangle = new PointF[] { new PointF(x, y)
                                            , new PointF(x + width, y)
                                            , new PointF(x + (width / 2), y + height) };
                pevent.Graphics.FillPolygon(brush, triangle);
            }
        }
    }
とりあえず、これで右クリックとダブルクリックでも問題ないはず。
一応、ボタンの右端に▼も描画してみたw

とりあえず今日はこんなところで終わり。

2013年8月3日土曜日

ブログを始めたはいいけど、、、

ブログを始めたはいいけど特に書くことも思いつかないまま、ずっと放り出していて気づけば1年以上が経っているw

しかも一度投稿したっきりっで。。。○| ̄|_

もっとも、始めた時から何か書きたいことがあったわけではなかったし、たぶんほとんど書くことはないだろうと思っていたんだけど、ここまで何もないとは。。。


でもまあ、心境の変化というわけでもないけど、久しぶりに書いてみようと思ったのは

予約していたLeap Motionが先日ようやく届いて、SDKもダウンロードして「さあ、いよいよなんか作ってみるか!」と思って調べてみたら、(当然だけど)日本語の情報がほとんどない!!!Σ(゚Д゚)

英語なんかほとんどわからないし、しょうがないので調べながらやってみようかなと、、、

で、どうせなら調べたことを記録するのに、ここが使えるなと思いついたのだ。


とはいえ、それもどうせ長くは続かないだろうし、もっとわかりやすい情報がそのうち現れるだろうから、ここではごくごく個人的に思いのままに書いてやろうかなと思うww

思いのままにというのはつまり、適当にというか間違っていようが情報が足りなかろうが、気にせず書いていこうという、言い訳のようなもので、

たぶんオレ以外にここを見ることはないだろうから、別に誰にでもなく未来の自分に対するまったく意味のない宣言をしてみたわけだ。



というわけで、とりあえずまずは環境構築の話から

と言っても、ここを参考にすれば、特に悩むこともなくできたので、わざわざ書くべきこともない。

ちょっとだけ引っかかったというか、ちゃんと書いてあるのに読み飛ばしてしまったことをメモしとこう。

まず、構成マネージャーでプラットフォームを「Any CPU」から「x86」にすること。これをしないと、Listenerクラスをnewする時に

「'Leap.LeapPINVOKE' のタイプ初期化子が例外をスローしました。」
「SWIGExceptionHelper' のタイプ初期化子が例外をスローしました。
間違ったフォーマットのプログラムを読み込もうとしました。」

なんて例外が発生する。

次に「LeapCSharp.dll」および「Leap.dll」を実行ファイルと同じフォルダにコピーしておく必要がある。これを忘れると


「'Leap.LeapPINVOKE' のタイプ初期化子が例外をスローしました。」
「SWIGExceptionHelper' のタイプ初期化子が例外をスローしました。
「DLL 'LeapCSharp' を読み込めません: 指定されたモジュールが見つかりません。」

と例外が発生してしまう。

どちらも、ここにはちゃんと書いてあることだし、ちゃんと読めば悩むこともなかったはずだけどw


とりあえず、最初はこんな感じで。

次回からは「C# and Unity」のAPIあたりを調べながら、日本語訳してみる予定(未定)ですwww

2012年4月30日月曜日

ブログはじめます!

思いついたことをまとまりなくメモって行くつもりです。

主にC#ネタを中心に不定期に更新する予定です。