.net」タグアーカイブ

Clipoli 0.0.2

Clipoli で読み込む ini ファイルはどこに置くか。
exe と同じ位置では昔の古い考え方バリバリなアプリになってしまう。
やはり環境変数 %APPDATA% に置くべきだろう。

echo %APPDATA%

と cmd.exe で打てばそれが何なのか解らない人も理解できるだろう。
Opera や Firefox の設定もココにありますね。

ということで書いてみる。
C# では Environment を使わないと環境変数展開は無理みたい、まぁ ini 側はイケるだろう。
SeeMe の設定で sasakima というディレクトリを作るので同じ所に置くように。

string appdata = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "sasakima");
if (!Directory.Exists(appdata))
{
	Directory.CreateDirectory(appdata);
}
appdata = Path.Combine(appdata, "clipoli");
if (!Directory.Exists(appdata))
{
	Directory.CreateDirectory(appdata);
}
appdata = Path.Combine(appdata, "default.ini");
if (!File.Exists(appdata))
{
	string s = "[Launcher]\r\ndefault.ini を開く=notepad %APPDATA%\\sasakima\\clipoli\\default.in";
	var sw = new StreamWriter(appdata);
	sw.Write(s);
	sw.Close();
}
var app = new TrayIcon(appdata);
Application.Run();

んでコンストラクタ引数に default.ini 位置を渡してコレを読み込むと。
こんな感じでいいかな、やってみる。

Process.Start じゃ環境変数の %APPDATA% を展開してくれない…

引数を半角スペースではなく別オーバーロード引数で渡さないと駄目みたい…

Linux に慣れすぎたのでコマンドラインと GUI の区別が付かなくなってしまった。
だって Linux はどちらも同じだもん、Windows では別物なんだよね。

えっと、%APPDATA% は自前で展開するしかないみたいなので Format するか。
コマンドを引数と分離するには、とりあえずファイル名に使えない | でも利用してみよう。

string appdata = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "sasakima");
if (!Directory.Exists(appdata))
{
	Directory.CreateDirectory(appdata);
}
appdata = Path.Combine(appdata, "clipoli");
if (!Directory.Exists(appdata))
{
	Directory.CreateDirectory(appdata);
}
appdata = Path.Combine(appdata, "default.ini");
if (!File.Exists(appdata))
{
	string s = "[Launcher]\r\ndefault.ini を開く=notepad|{0}";
	s = string.Format(s, appdata);
	var sw = new StreamWriter(appdata);
	sw.Write(s);
	sw.Close();
}
var app = new TrayIcon(appdata);
Application.Run();

これで

private void on_launcher(object sender, EventArgs e)
{
	try
	{
		string s = (sender as ToolStripMenuItem).Name;// MessageBox.Show(s);
		int lPos = s.IndexOf('|');
		if (lPos == -1)
		{
			System.Diagnostics.Process.Start(s);
		}
		else
		{
			string name = s.Substring(0, lPos);
			string args = s.Substring(lPos + 1, s.Length - lPos - 1);
			System.Diagnostics.Process.Start(name, args);
		}
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.Message);
	}
}

と分離して Process.Start する、意外に面倒な処理になっちまったなぁ。
後は多重起動防止させて、えっとそれから…
あぁアイコンを作る時間も無かった、つか十二時過ぎた、とりあえずバックアップ。

clipoli002.zip

今日は日経平均株価は上がってくれるだろうか…
木金で今月の稼ぎが吹っ飛んでもーたんだが…

Clipoli 0.0.1

Windows アプリ作りの為に新しいノートを買うか迷っている。
が、よく考えたら VirtualBox 仮想マシンの Vista があるじゃないか。
Aero が使えず遅いので全然使わなかったから存在を忘れる所だった。

これを使えばホストの Ubuntu で使っているマジェスタッチで打てる。
あぁキーボード入力が楽々!

しかもコイツには Visual Studio 2008 pro をインストールしてある。
Debug が楽々、コード保管で楽々、リファクタで楽々、何もかもが楽々!
流石はアップグレードに六万円も払っただけはある、これぞ統合開発環境。
テキストエディタだけで開発をしばらくやった後で触るとマジで神アプリ。

でも基本的にこんなものをインストールしないで作れるアプリにしたい。
ので bat の W クリックだけでビルドできるようにアプリ作成を続ける。

とにかく clipoli というタスクトレイ常駐するアプリだ。
当面はランチャとクリップボードにテキストを入れる機能を本格的に。
カスタマイズが簡単にできるように何かのファイルからデータを入れる。

よし、INI ファイルだ。

あぁ Opera 使いの性。

Opera 屋以外はアドオンと考えそう…

ま、仕様をアッサリ決められるかどうかがプロと凡人の違い。
なんだけど後悔することのほうが多いのは書くまでもない。

[Launcher]
メモ帳=C:\Windows\notepad.exe
端末=C:\Windows\System32\cmd.exe
MSDN=http://msdn.microsoft.com/ja-jp/library/

[Clipboard]
けいおん=あずにゃん
俺妹=きりの
えむえむ=みおたん
声優=全部同じ人だった...

みたく INI を作り読み込んでメニューの作成という流れでとりあえずいこう。
C# での INI 読み書きは以前作っているから簡単だ。

UTF-8 の ini 読み書き

と思ったけど今見ると書いていることが少々古い…
てか MessageBox をココで使うと WindowsForm だと using が足らない。
Python で作ったの同様に間違いは例外を投げるように変更して書き換えなきゃ。

そんなことより、このアプリの場合はこんな複雑なコードいらないヤン…

iniread.cs

using System;
using System.IO;
using System.Collections.Generic;
using System.Text;

namespace IniFile
{
	public struct Ini
	{
		public string section {get; set;}
		public string key {get; set;}
		public string value {get; set;}
	}
	public static class IniReader
	{
		public static IEnumerable<Ini> ReadLines(string path)
		{
			if (File.Exists(path))
			{
				string section = "";
				using (var sr = new StreamReader(path, Encoding.UTF8))
				{
					string buf;
					while ((buf = sr.ReadLine()) != null)
					{
						if (buf == "") continue;
						if (buf[0] == ';') continue;
						int len = buf.Length;
						if (len > 2 && buf[0] == '[' && buf[len - 1] == ']')
						{
							section = buf.Substring(1, len - 2);
						}
						else if (section == "")
						{
							//何もしない
						}
						else
						{
							int lPos = buf.IndexOf('='); //見つからない場合は -1
							if (lPos > 0)
							{
								Ini ini = new Ini();
								ini.section = section;
								ini.key = buf.Substring(0, lPos);
								ini.value = buf.Substring(lPos + 1, len - lPos - 1);
								yield return ini;
							}
						}
					}
				}
			}
		}
	}
}

これで十分だった、struct を yield で戻せば必要な値は取れるし。
んでコレを展開するコードを。

mainsrc.cs

using System;
using System.IO;
using System.Windows;
using System.Windows.Forms;
using System.Drawing;
using System.Reflection; // MethodInfo
using IniFile;

namespace Clipoli
{
	class TrayIcon
	{
		private NotifyIcon _icon;
		private ContextMenuStrip _menu;
		public TrayIcon()
		{
			_menu = new ContextMenuStrip();
			int i = 0;
			foreach (var ini in IniReader.ReadLines("default.ini"))
			{
				// Load Launcher
				if (ini.section == "Launcher")
				{
					_menu.Items.Add(ini.key, null, on_launcher);
					_menu.Items[i].Name = ini.value;
					i++;
				}
				// Load Clipboard
				else if (ini.section == "Clipboard")
				{
					_menu.Items.Add(ini.key, null, on_clipboard);
					_menu.Items[i].Name = ini.value;
					i++;
				}
				else
				{
					MessageBox.Show("現在 Launcher と Clipboard 以外のセクションは無効です");
				}
			}
			_menu.Items.Add("-");
			_menu.Items.Add("バージョン情報", null, on_about);
			_menu.Items.Add("-");
			_menu.Items.Add("終了", null, on_exit);
			// create tray icon
			_icon = new NotifyIcon();
			_icon.ContextMenuStrip = _menu;
			_icon.Icon = Icon.ExtractAssociatedIcon("clipoli.exe");
			_icon.Text = "Description Text";
			_icon.Visible = true;
			_icon.MouseUp += on_click;
		}

		private void on_click(object sender, MouseEventArgs e)
		{
			if (e.Button == MouseButtons.Left)
			{
				MethodInfo mi = typeof(NotifyIcon).GetMethod("ShowContextMenu",
								BindingFlags.Instance | BindingFlags.NonPublic);
				mi.Invoke(_icon, null);
			}
		}
		private void on_launcher(object sender, EventArgs e)
		{
			try
			{
				string s = (sender as ToolStripMenuItem).Name;
				System.Diagnostics.Process.Start(s);
			}
			catch (Exception ex)
			{
				MessageBox.Show(ex.Message);
			}
		}
		private void on_clipboard(object sender, EventArgs e)
		{
			try
			{
				string s = (sender as ToolStripMenuItem).Name;
				Clipboard.SetText(s);
				System.Media.SystemSounds.Beep.Play();
			}
			catch (Exception ex)
			{
				MessageBox.Show(ex.Message);
			}
		}
		private void on_about(object sender, EventArgs e)
		{
			var about = new About(_icon.Icon);
			about.ShowDialog();
		}
		private void on_exit(object sender, EventArgs e)
		{
			_icon.Visible = false;
			Application.Exit();
		}
	}
	class __main__
	{
		[STAThread]
		public static void Main(string[] args)
		{
			var n = new TrayIcon();
			Application.Run();
		}
	}
}

ContextMenu は ContextMenuStrip に変更したほうが扱いやすいみたいだったので。

その ContextMenuStrip.Name プロパティに文字列を入れるというウソみたいなことをやった。
だってこれで動くもの、sender から簡単に文字列が得られるし。

トレイアイコンの左クリックでメニューを出す方法は下記をパクった。
NotifyIconの左クリックでコンテキストメニューを表示させる:Gushwell’s C# Dev Notes

見えない Form をアクティブにするなんて方法も見つけたけど上手くいかなかった。
というか、こんなに強引なのに一番簡単かつ短いコードで終わるという。

想像していたより遥かに短いコードでこれだけ実装できてしまった。
私的にはもうコレで十分、ini の書き方が解らない人なんていないだろうし。
後は何を付けよう、というわけでまとめて zip

clipoli001.zip

しかしマジで Windows マシンはどうしよう?
キーボードでノートを選ぶなら東芝 R730 か Lenovo X201s がよさそうだが…
11.6 型の今でも持ち運びする時に少々大きいし…
1366×768 は正直狭いのでせめて 1600×900 以上にしたいけど…
かといってデスクトップでは使用歩度から考えて邪魔だし…
ブルーレイの再生をしたくなったら動画再生支援機能に問題が無い Windows のほうが…
結局決まらないのでミニノート 7 と仮想マシンの XP, Vista のままになりそう。

Clipoli 0.0.0

IronPython を Windows 7 から削除の予定。
言語としては素晴らしいのだがなにせ遅い、我慢するのはもう限界だ。
さて困った、こいつは自分で利用しているからなんとかしなければ。

NotifyIcon to use from IronPython

C# で作り変えするしか無い、いや C++ でも作れるけど何故かやりたくない。
WindowsForm を使うので同じなんだが .NET で作りたいので。

せっかく作り変えるのだし exe にまとめるのだし、もう少し拘りたい。

まずはアイコンを埋め込みしたい、これは Python じゃ無理だったので。
しかし Form は使わないので this.Icon からトレイ用のアイコンを取得できない。

Icon.ExtractAssociatedIcon メソッド (System.Drawing)

なんだ、exe からアッサリ取得できるみたい。
ということは自分自身の exe 名を指定だけでイケそうだ。
アイコンの指定は csc.exe への引数で埋め込みできるもんね。

どうでもいいかもしれないけど exe 名の名前空間に収めたいな。
exe 名は、minipoli 同様なミニアプリだから clipoli にしよう。

そう、Windows 用の新規アプリで公開してしまおうと、三年ぶりか。
Cinema とかのもう自分が使っていないアプリの公開を終了するか悩む所。

そうそう、バージョン情報ダイアログも追加しなきゃ。
これは SeeMe v4 の時に作ったのを持ってきて、って SeeMe v4 は WPF だ。
流用すると WindowsForm と混在になるので名前空間がややこしくなる…

しかたがない、 Form でテキトーに作ってみるか。

about.cs

using System;
using System.Windows;
using System.Windows.Forms;
using System.Drawing;

namespace Clipoli
{
	class About : Form
	{
		public About(Icon icon)
		{
			this.Text = "About Clipoli";
			this.Icon = icon;
			this.Width = 300;
			this.Height = 200;
			var label = new Label();
			label.Location = new Point(10, 10);
			label.Text = "情報";
			this.Controls.Add(label);
			var button = new Button();
			button.Location = new Point(10, 100);
			button.Text = ("OK");
			button.Click += on_ok;
			this.Controls.Add(button);
		}
		void on_ok(object sender, EventArgs e)
		{
			Close();
		}
	}
}

WindowsForm が嫌いだからといえ我ながらテキトーすぎる。
それはそれとしてメインソース。

mainsrc.cs

using System;
using System.IO;
using System.Windows;
using System.Windows.Forms;
using System.Drawing;

namespace Clipoli
{
	class TrayIcon
	{
		private NotifyIcon _icon;
		public TrayIcon()
		{
			var menu = new ContextMenu();
			menu.MenuItems.Add("メモ帳", on_notepad);
			menu.MenuItems.Add("クリップボード", on_clipboard);
			menu.MenuItems.Add("-");
			menu.MenuItems.Add("バージョン情報", on_about);
			menu.MenuItems.Add("-");
			menu.MenuItems.Add("終了", on_exit);
			// create tray icon
			_icon = new NotifyIcon();
			_icon.ContextMenu = menu;
			_icon.Icon = Icon.ExtractAssociatedIcon("clipoli.exe");
			_icon.Text = "Description Text";
			_icon.Visible = true;
		}
		private void on_notepad(object sender, EventArgs e)
		{
			System.Diagnostics.Process.Start("notepad.exe");
		}
		private void on_clipboard(object sender, EventArgs e)
		{
			try
			{
				Clipboard.SetText("あずにゃん");
				System.Media.SystemSounds.Beep.Play();
			}
			catch (Exception ex)
			{
				MessageBox.Show(ex.Message);
			}
		}
		private void on_about(object sender, EventArgs e)
		{
			var about = new About(_icon.Icon);
			about.ShowDialog();
		}
		private void on_exit(object sender, EventArgs e)
		{
			_icon.Visible = false;
			Application.Exit();
		}
	}
	class __main__
	{
		[STAThread]
		public static void Main(string[] args)
		{
			var n = new TrayIcon();
			Application.Run();
		}
	}
}

アイコンは IronPython 用に作ったのを仮で置いてビルドバッチ。
WindowsForm で .net 4.0 は馬鹿馬鹿しいので 3.5 の csc exe で。

build.bat

C:\Windows\Microsoft.NET\Framework\v3.5\csc.exe ^
/reference:System.Windows.Forms.dll ^
/reference:System.Drawing.dll ^
/target:winexe ^
/out:clipoli.exe ^
/win32icon:icon.ico ^
/optimize+ ^
*.cs
PAUSE

まとめて ZIP
clipoli000.zip

まだテスト段階だしこんな感じでいいだろう。
動かしてみる、まあ IronPython 作の時とほぼ同じだし当然動くか。

問題はトレイアイコンの右クリックしか受け付けないことなんだよな。
自分で使うだけの分にはそれでいいのだが公開するとなると…

というか、ミニノートで作るのが少々辛くなってきた。
意地をはってもしかたがないし、もう少しマシな Windows を買おうかなと。

Fake System.IO

System.IO.File.ReadLines は .NET 3.5 や mono で使えない。
だったら自分で作ってしまえ、と思ったのでやってみた。

どうでもいいけど gmcs も csc.exe と同じオプションが使えるんだね。
/out:FILENAME は -out:FILENAME みたくハイフンにするだけ。

いや、yield を使ったことが無いのでやってみようかと。
こんな強引というよりアホなメソッド追加なんてできるのかな?

using System;
using System.Collections.Generic; //IEnumerable

namespace System.IO
{
    public static partial class File
    {
        public static IEnumerable ReadLines(string path)
        {
            using(var sr = new System.IO.StreamReader(path))
            {
                string buf;
                while ((buf = sr.ReadLine()) != null)
                {
                    yield return buf;
                }
            }
        }
    }
}

File クラス (System.IO)
File クラスは partial class では無いみたいだけど。

コンフリクトか、やっぱりダメだった。
Windows でもやってみたけど同じ、こんなことをやろうと考えるほうがおかしい。
しかたがない、名前空間には追加できるはずだから違うクラス名にしてみる。

fakeio.cs

using System;
using System.Collections.Generic; //IEnumerable

namespace System.IO
{
    public static class FakeFile
    {
        public static IEnumerable ReadLines(string path)
        {
            using(var sr = new System.IO.StreamReader(path))
            {
                string buf;
                while ((buf = sr.ReadLine()) != null)
                {
                    yield return buf;
                }
            }
        }
    }
}

readlines.cs

using System;
using System.IO;

class __main__
{
    [STAThread]
    public static void Main(string[] args)
    {
        if (args.Length == 0)
        {
            Console.WriteLine("no arg...");
            return;
        }
        int n = 0;
        foreach (var line in FakeFile.ReadLines(args[0]))
        {
            n++;
            Console.WriteLine("{0}: {1}", n.ToString("000"), line);
        }
    }
}

で、cat.exe という実行ファイルを作るコマンド。

gmcs -out:cat.exe *.cs

何故か exe という拡張子を付けないとエラーになる。
Linux でも強制するんかい、まぁ解りやすいと思えばいいし。
アスタリスクでディレクトリ内をまるごとコンパイルしてくれるのはありがたい。

なるほど、yield はこういう時に使うと確かに便利だ。
C#4.0 の ReadLines って上記を自作する以上のメリットがあるのかな?