Programming」カテゴリーアーカイブ

WPF Simple TextEditor example

あけおめことよろ、遅い?
さて今年もプログラミングてゆーかソフトウエア作るぞと。

ところで前回の IronPython でタスクトレイは結構人が来ている、実際自分で利用しているし。
やっぱり Windows でのネタでないとアクセスが稼げないなと実感しまくる。
そういえば以前こんなのを書いたけど

ぱぇぽぃ2 ? Blog Archive ? IronPython は親クラスの __init__() がいらない

IronPython での open は StreamReader と同じなのかな?
なんて思ったので実験コードを書いて試してみた。

結果だけではつまらないのでシンプルなテキストエディタを作ってみる。
海外から結構見に来るので全部英語で書くけど日本語向けコードだと解るように。
ちなみに Google 翻訳を駆使しているだけなので翻訳が合っているかどうか解らない。

# -*- coding: UTF-8 -*-

"""
    This Code is Suitable for Japanese
    XAML [xml:lang] Own Rewrite
    Read and Write encoding is UTF-8 Non BOM Text
"""

import clr

clr.AddReferenceByPartialName("PresentationCore")
clr.AddReferenceByPartialName("PresentationFramework")
clr.AddReferenceByPartialName("WindowsBase")
# Open,SaveFileDialog
clr.AddReferenceByPartialName("System.Windows.Forms")

from System import *
from System.IO import *
from System.Windows import *
from System.Windows.Controls import *
from System.Windows.Input import *
from System.Windows.Controls.Primitives import *
from System.Windows.Markup import XamlReader
# avoid namespace conflict
from System.Windows.Forms import OpenFileDialog, SaveFileDialog, DialogResult

menu_str = """<Menu
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xml:lang="ja-JP">
    <MenuItem Header="_File">
        <MenuItem Header="_Open" InputGestureText="Ctrl+O" Name="menu_open" />
        <MenuItem Header="_Save" InputGestureText="Ctrl+S" Name="menu_save" />
        <MenuItem Header="Save _As" Name="menu_save_as" />
        <Separator/>
        <MenuItem Header="_Quit" InputGestureText="Ctrl+Q" Name="menu_close" />
    </MenuItem>
</Menu>"""

class WPF_TextEditor(Window):
    """
        Simple TextEditor
    """
    def __init__(self):
        """
            Initialization
        """
        self.openfile = ""
        # Menu
        menu = XamlReader.Parse(menu_str)
        menu.FindName("menu_open").Click += self.on_open
        menu.FindName("menu_save").Click += self.on_save
        menu.FindName("menu_save_as").Click += self.on_save_as
        menu.FindName("menu_close").Click += self.on_close
        DockPanel.SetDock(menu, Dock.Top)
        # TextBox
        self.textbox = TextBox()
        self.textbox.TextWrapping = TextWrapping.NoWrap
        self.textbox.AcceptsReturn = True
        self.textbox.VerticalScrollBarVisibility = ScrollBarVisibility.Auto
        self.textbox.HorizontalScrollBarVisibility = ScrollBarVisibility.Auto
        # StatusBar
        statusbar = StatusBar()
        DockPanel.SetDock(statusbar, Dock.Bottom)
        self.statusbar_item = StatusBarItem()
        self.statusbar_item.Content = "no open"
        statusbar.Items.Add(self.statusbar_item)
        # DockPanel
        dpanel = DockPanel()
        dpanel.Children.Add(menu)
        dpanel.Children.Add(statusbar)
        dpanel.Children.Add(self.textbox)
        # self
        self.Content = dpanel
        self.Title = "WPF_TextEditor"
        # event
        self.KeyDown += self.on_keydown
        # Drag and drop
        self.AllowDrop = True
        self.PreviewDragOver += self.on_predrop
        self.Drop += self.on_drop

    def on_open(self, sender, e):
        """
            namespace Microsoft Dialog is Old
        """
        dlg = OpenFileDialog()
        dlg.Title = "OpenFile"
        if dlg.ShowDialog() == DialogResult.OK:
            self.read_file(dlg.FileName)

    def on_save(self, sender, e):
        """
            is Open ?
        """
        if self.openfile == "":
            self.on_save_as(sender, e)
        else:
            self.save_file(self.openfile)

    def on_save_as(self, sender, e):
        """
            namespace Microsoft Dialog is Old
        """
        dlg = SaveFileDialog()
        dlg.Title = "SaveFile"
        if dlg.ShowDialog() == DialogResult.OK:
            self.save_file(dlg.FileName)

    def on_close(self, sender, e):
        """
            Bye
        """
        self.Close()

    def on_keydown(self, sender, e):
        """
            InputGestureText is Show only
            e is System.Windows.Input.KeyEventArgs
        """
        if Keyboard.Modifiers == ModifierKeys.Control:
            if e.Key == Key.O:
                self.on_open(self, e)
            elif e.Key == Key.S:
                self.on_save(self, e)
            elif e.Key == Key.Q:
                self.on_close(self, e)

    def on_predrop(self, sender, e):
        """
            PreviewDragOver Event
        """
        e.Handled = e.Data.GetData(DataFormats.FileDrop) != None

    def on_drop(self, sender, e):
        """
            File Dropped
        """
        filenames = e.Data.GetData(DataFormats.FileDrop)
        filename = filenames[0].ToString()
        if filename != None and filename.Length != 0:
            self.read_file(filename)

    def read_file(self, filename):
        """
            f = open(filename)
            try:
                s = f.read()
                self.textbox.Text = unicode(s, "utf8")
            except Exception, ex:
                MessageBox.Show(ex.Message)
            finally:
                f.close()
        """
        sw = StreamReader(filename) 
        try:
            self.textbox.Text = sw.ReadToEnd()
            self.statusbar_item.Content = Path.GetFileName(filename)
            self.openfile = filename
        except Exception, ex:
            MessageBox.Show(ex.Message)
        finally:
            sw.Close()

    def save_file(self, filename):
        """
            f = open(filename, "w")
            try:
                f.write(self.textbox.Text.encode("utf8"))
            except Exception, ex:
                MessageBox.Show(ex.Message)
            finally:
                f.close()
        """
        sw = StreamWriter(filename) 
        try:
            buf = self.textbox.Text
            sw.Write(buf)
            self.statusbar_item.Content = Path.GetFileName(filename)
            self.openfile = filename
        except Exception, ex:
            MessageBox.Show(ex.Message)
        finally:
            sw.Close()

if __name__ == "__main__":
    Application().Run(WPF_TextEditor())

StreamReader で encode 無指定だと BOM 無し UTF-8 で読み書きになる。
けど open() では変換が必要なんだね、標準 Python に合わせたのだろうけど。
面倒くさいや、StreamReader を使ったほうが何かと便利そうだ。

メニューだけ XAML な所とか細かい所はこの Blog 過去ログを見て書いた。
結構色々やっていたんだな我ながら、まとめを兼ねて書いたようなものだ。

気が付いたけどこれだけで Ctrl+Z でのやり直しやテキストの D&D 編集ができる。
GtkSourceView ほど高機能ではないけど以外に使えるんでないかい TextBox って。

NotifyIcon to use from IronPython

IronPython スクリプトを快適に使いたい。
version 2.6 から Python モジュールも同梱になり更に便利になった。
そして .NET Framework のパワーがほぼ全てスクリプトのみで記述できる。
別途 SDK のインストールは不要、exe へのコンパイルも不要、言うことなし。

しかし欠点がある、初期化、つまり初回起動が遅すぎることである。
ならばタスクトレイに常駐させてしまえ!

MSDN: ContextMenu クラス (System.Windows.Forms)
MSDN: NotifyIcon クラス (System.Windows.Forms)
チュートリアル : Windows フォームの動的なコンテキスト メニューの作成

システムトレイ(タスクトレイ)にアイコンを表示するには? ? @IT
起動時にタスクトレイのアイコンのみを表示するには? ? @IT

なんかを参考に作ってみた、WindowsForm に頼るしか方法が無いみたい。
これじゃ当分 Microsoft はこの名前空間を外すことはできないな。

self.menu.MenuItems.Add("Exit", EventHandler(self.on_exit))

とする必要は無いみたい、PyGtk と同様に簡単に書けるのが IronPython の魅力。

しかし WindowsForm の Application クラスはインスタンスを作成しなくてもいいんだね。
WPF ばかりやっていたからチト調子が狂う。

ということでやってみた例。

notify_icon.pyw

# -*- coding: UTF-8 -*-

import clr

clr.AddReferenceByPartialName("System.Windows.Forms")
clr.AddReferenceByPartialName("System.Drawing")

from System import *
from System.IO import *
from System.Windows import *
from System.Windows.Forms import *
from System.Drawing import *

class NfIcon():
    """
        Task Tray Icon
    """
    def __init__(self):
        """
            Initialization
        """
        self.path = Path.GetDirectoryName(__file__)
        # create menu
        self.menu = ContextMenu()
        self.menu.MenuItems.Add("NotePad", self.on_notepad)
        self.menu.MenuItems.Add("Clipboard", self.on_clipboard)
        self.menu.MenuItems.Add("External file Execute", self.on_extern)
        self.menu.MenuItems.Add("-")
        self.menu.MenuItems.Add("Exit", self.on_exit)
        # create tray icon
        # Not inherit. this class is Sealed
        self.icon = NotifyIcon()
        self.icon.ContextMenu = self.menu
        self.icon.Icon = Icon(self.path + "\\icon.ico")
        self.icon.Text = "Description Text"
        self.icon.Visible = True

    def on_notepad(self, sender, e):
        """
            case Lancher
        """
        try:
            import os
            os.system("notepad.exe")
        except Exception, e:
            MessageBox.Show(e.Message)

    def on_clipboard(self, sender, e):
        """
            case Clipboard and Beep
        """
        try:
            Clipboard.SetText("Clipboard Text")
            Media.SystemSounds.Beep.Play()
        except Exception, e:
            MessageBox.Show(e.Message)

    def on_extern(self, sender, e):
        """
            case External file Execute
        """
        try:
            import extern
        except Exception, e:
            MessageBox.Show(e.Message)

    def on_exit(self, sender, e):
        """
            bye
        """
        self.icon.Visible = False
        Application.Exit()

if __name__ == "__main__":
    # Not Application.Run(NfIcon())
    NfIcon()
    Application.Run()

extern.py

# -*- coding: UTF-8 -*-

import clr

clr.AddReferenceByPartialName("System.Windows.Forms")

from System.Windows.Forms import *

MessageBox.Show("this File is extern.py")

ヘタクソな icon を同梱して zip にまとめたのも置いておくね。

notify_icon.zip

で、ipyw(64).exe にて notify_icon.pyw を起動させると常駐する。
アイコンを右クリックするとメニューが出るので選択すればよい。

常駐しているので初期化が必要なくなり高速にスクリプトを実行できる。
スタートアップに登録すれば簡単に使えるね、関連付けしない人は bat を作ればいい。

後は動的なスクリプト読み込みができるようにすれば理想的。
とはいえコレなら追記するのも簡単なのでそんな仕組みは不要かなとも思う。

ということで久々に書いた具体的アプリケーションコードにて本年を終わります。

Double.NaN

びっくりした事

あれ?C# って Double ならモロに 0 で除算してもコンパイルエラーにならないんだ。
clr なら同じかなと思って IronPython で為してみたけど実行エラーが出るだけだった。
てか文法が合っているなら普通の Python でもこの強引なコードで読み取りは通るんだね。

def check():
    print 0/0
input("wait: ")
check()

C# も為してみようと思ったけど仮想 Vista を立ち上げるのが面倒なので mono でやってみる。
Mandriva One GNOME は gcc がデフォでは入っていないくせに gmcs はしっかりあるんだよね。
ま、最近の GNOME なら全部入っているだろうけど。

ということで

Console.WriteLine(0/0);

だと mono でもしっかりコンパイルエラーになります、忠実に再現しているのね。

Double.NaN フィールド (System)

コレも当然動く。
こういうのって便利なのだろうか?普通に例外を吐くだけのほうが楽だと思うのだが。

プログラミングの良さげな Q&A サイトめっけ

最近 PyGtk ネタを書いていないので。
ちと海外を探していて良さげな Q&A サイトを見つけた。
プログラミング系全般を扱っているがタグで分けているので解りやすい。

Stack Overflow

Python だけで現在 15,000 もあるし PyGtk だけをタグから探すことも可能。

Hottest ‘pygtk’ Questions – Stack Overflow

全体的に Windows 環境でのことが多いのは当然だよと。
ついでにソコを見ていて見つけたんだが。

GNOME

busybox.py のコードに衝撃!こんなことができたんだ。

busybox

それと pygtk-docs.tar.gz を展開すればローカルでドキュメントが見られる。
今まで Web で全部読んでいたから軽い local で観覧できるのは嬉しいよ。
実は Fedora ならデフォルトで DevHelp にコレは入っているんだけーが…

local_manual

ところで pygoo って何?と思ったので探してみたけど

pygoo – Project Hosting on Google Code

利用したい人っているのかなぁ…
標準オブジェクトを継承して自分で拡張できなきゃ小物すら作れないと思うんだが。
上記の busybox.py を見て本当にそう思う。

Own class library to use from IronPython

IronPython を日本で検索すると C# から IronPython を使う方法ばかりだ。
GTK+ における PyGtk のように皆が使ってくれるだろうとか考えているのかな?
Windows ユーザーがそんなことをするとでも思って…(略
Windows 用オンラインソフト作者をやれば解るって…

よし。
逆に C# で作った自作クラスライブラリを IronPython から使う方法でもやるか。
需要が微妙であるのは気にしない、どうしてもそうしたい場合もあるヤン!

とりあえず Visual Studio でテキトーなクラスライブラリを作る。

using System;

namespace ClassLibrary1
{
    public class Class1
    {
        public static bool ChangeText(out string s)
        {
            s = "Change";
            return true;
        }
    }
}

classlibrary_build

意図的に out を利用している。
IronPython で ref や out を利用するにはこう書く解説も兼ねている。
下記サイトが色々と参考になることを書いてくれてているよと。

Dark Corners of IronPython

んでソレをコードと同一ディレクトリに置く。
肝心なコード。

# -*- coding: UTF-8 -*-

import clr
clr.AddReference("ClassLibrary1")
import ClassLibrary1

result, value = ClassLibrary1.Class1.ChangeText()
print result
print value

cs_lib.zip

cs_lib_py

という感じで利用できます。
サードパーティ製や自作のライブラリが使いたい場合にコレでなんとかなると思う。