C#|ユーザーコントロールをD&Dで移動&サイズ変更

[C#]ユーザーコントロールをドラッグアンドドロップで移動で面白そうなことやってるので書いてみました。結構泥臭くなりますね。ちょっと嵌ってしまって時間が掛かったのはここだけの秘密。

using System.Drawing;
using System.Windows.Forms;
public partial class UserControl1 :UserControl {
    Size lastMouseDownSize;
    Point lastMouseDownPoint;
    DAndDStatus status;
    int sizeChangeArea = 8;
    public UserControl1() {
        InitializeComponent();
    }
    protected override void OnMouseDown(MouseEventArgs e) {
        lastMouseDownPoint = e.Location;
        lastMouseDownSize = this.Size;
        //動作を決定
        status = DAndDStatus.None;
        if(getTop().Contains(e.Location)) {
            status |= DAndDStatus.Top;
        }
        if(getLeft().Contains(e.Location)) {
            status |= DAndDStatus.Left;
        }
        if(getBottom().Contains(e.Location)) {
            status |= DAndDStatus.Bottom;
        }
        if(getRight().Contains(e.Location)) {
            status |= DAndDStatus.Right;
        }
        if(status == DAndDStatus.None) {
            status = DAndDStatus.Move;
        } else {
            this.Capture = true;
        }
        base.OnMouseDown(e);
    }
    protected override void OnMouseMove(MouseEventArgs e) {
        //カーソルを変更
        if((getTop().Contains(e.Location) && 
            getLeft().Contains(e.Location)) ||
            (getBottom().Contains(e.Location) && 
            getRight().Contains(e.Location))) {
            this.Cursor = Cursors.SizeNWSE;
        } else if((getTop().Contains(e.Location) && 
            getRight().Contains(e.Location)) ||
            (getBottom().Contains(e.Location) && 
            getLeft().Contains(e.Location))) {
            this.Cursor = Cursors.SizeNESW;
        } else if(getTop().Contains(e.Location) || 
            getBottom().Contains(e.Location)) {
            
            this.Cursor = Cursors.SizeNS;
        } else if(getLeft().Contains(e.Location) || 
            getRight().Contains(e.Location)) {
            
            this.Cursor = Cursors.SizeWE;
        } else {
            this.Cursor = Cursors.Default;
        }
        if(e.Button == MouseButtons.Left) {
            int diffX = e.X - lastMouseDownPoint.X;
            int diffY = e.Y - lastMouseDownPoint.Y;
            if((status & DAndDStatus.Move) == DAndDStatus.Move) {
                this.Left += diffX;
                this.Top += diffY;
            }
            if((status & DAndDStatus.Top) == DAndDStatus.Top) {
                int h = this.Height;
                this.Height -= diffY;
                this.Top += h - this.Height;
            }
            if((status & DAndDStatus.Bottom) == DAndDStatus.Bottom) {
                this.Height = lastMouseDownSize.Height + diffY;
            }
            if((status & DAndDStatus.Left) == DAndDStatus.Left) {
                int w = this.Width;
                this.Width -= diffX;
                this.Left += w - this.Width;
            }
            if((status & DAndDStatus.Right) == DAndDStatus.Right) {
                this.Width = lastMouseDownSize.Width + diffX;
            }
        }
        this.Refresh();
        base.OnMouseMove(e);
    }
    protected override void OnMouseUp(MouseEventArgs e) {
        this.Capture = false;
        base.OnMouseUp(e);
    }
    protected override void OnPaint(PaintEventArgs e) {
        e.Graphics.DrawRectangle(
            Pens.Black,
            0, 0,
            this.Width - 1,
            this.Height - 1);
        base.OnPaint(e);
    }
    private Rectangle getTop() {
        return new Rectangle(0, 0, this.Width, sizeChangeArea);
    }
    private Rectangle getBottom() {
        return new Rectangle(0, this.Height - sizeChangeArea,
            this.Width, sizeChangeArea);
    }
    private Rectangle getLeft() {
        return new Rectangle(0,0,
            sizeChangeArea, this.Height);
    }
    private Rectangle getRight() {
        return new Rectangle(this.Width - sizeChangeArea, 0,
            sizeChangeArea, this.Height);
    }
    private enum DAndDStatus {
        None = 0,
        Move = 1,
        Top = 2,
        Bottom = 4,
        Left = 8,
        Right = 16,
    }
}

C#|テキストボックス上でのD&Dでフォームを移動する

TextBoxコントロール上でのドラッグ&ドロップでフォームを移動させようとした場合、文字列を選択する操作とバッティングしないようにする必要がある。

以下の実装で良い感じに動いた。

Point mouseDownPoint = new Point();
bool dAndDMoveFlag = false;
private void textBox1_MouseDown(object sender, MouseEventArgs e) {
    if(e.Button != MouseButtons.Left) return;
    mouseDownPoint = e.Location;
    bool flag = true;
    int charHeight = 
        TextRenderer.MeasureText("A", this.textBox1.Font).Height;
    
    for(int i = 0; i < this.textBox1.Lines.Length; i++) {
        Point point = new Point(0, i * charHeight);
        Size size = 
            TextRenderer.MeasureText(
                this.textBox1.Lines[i], 
                this.textBox1.Font);
        Rectangle rect = new Rectangle(point, size);
    
        if(rect.Contains(e.Location)) {
            flag = false;
            break;
        }
    }
    
    this.dAndDMoveFlag = flag;
}
private void textBox1_MouseMove(object sender, MouseEventArgs e) {
    if(dAndDMoveFlag) {
        Point p = this.Location;
        p.Offset(
            new Point(e.X - mouseDownPoint.X, e.Y - mouseDownPoint.Y));
        this.Location = p;
    }
}
private void textBox1_MouseUp(object sender, MouseEventArgs e) {
    dAndDMoveFlag = false;
}


 

素早くドラッグ&ドロップしたときに文字が選択されてしまいフォームの移動がキャンセルされるのを回避する為にdAndDMoveFlag(センスねー名前w)でフラグを立てている。

あとはそんなに難しいことしてないと思う。

※textBox1のTextが空の場合にLinesプロパティの境界外を参照して例外が発生していたので修正した。

※気が変わったので違うロジックでゴッソリと書き換えた。

C#|System.Windows.Forms.ColorDialogのバグ?

ColorDialogクラスのCustomColorsプロパティが期待通りに動作しない。

フォームの背景色をColor.FromArgb(50, 100, 150)に設定している。ちなみにこんな色。

ClpBdImg0[1]

ボタンがクリックされるとColorDialogのCustomColorsプロパティにColor.FromArgb(50, 100, 150)を設定して表示する。

おかしい、追加した色が黒く表示されている。色の作成ボタンを押してRGB値を見てみると

ColorDialogBug2[1]

RとBが反転している。これは困った。

using System;

using System.Drawing;

using System.Windows.Forms;

public partial class Form1 :Form {

    Color color = Color.FromArgb(50, 100, 150);

    public Form1() {

        InitializeComponent();

        this.BackColor = color;

    }

    private void button1_Click(object sender, EventArgs e) {

        this.colorDialog1.CustomColors = new int[] { color.ToArgb() };

        this.colorDialog1.ShowDialog();

    }

}

ここに解決策があった。

http://www.atmarkit.co.jp/bbs/phpBB/viewtopic.php?topic=14475&forum=7

ToArgbではなくColorTranslator.ToWin32()を使えばいいとのこと。これで期待通りに動作した。

めでたしめでたし。

C#|IronPython|C#でIronPython2.0をホスティングする

http://www.voidspace.org.uk/ironpython/ip_in_ip.shtml (英語)

英語は読めないからソースだけ読んで参考にしました。

【参照設定】

IronPython2.0のディレクトリより

IronPython.dll

Microsoft.Scripting.Core.dll

Microsoft.Scripting.dll

【Form1.cs】

using System;

using System.Windows.Forms;

using IronPython.Hosting;

using Microsoft.Scripting.Hosting;

public partial class Form1 :Form {

  public Form1() {

    InitializeComponent();

  }

  private void button1_Click(object sender, EventArgs e) {

    ScriptEngine engine = Python.CreateEngine();

    ScriptSource source =

        engine.CreateScriptSourceFromFile(“./test.py”);

    ScriptScope scope = engine.CreateScope();

    //IronPythonにformという変数名でthisを渡す。

    scope.SetVariable(“form”, this);

    //ここで実行

    source.Execute(scope);

    //IronPythonのtestVarという変数のオブジェクトを取得する。

    object value = scope.GetVariable(“testVar”);

    this.button1.Text = (string)value;

  }

}

【test.py】

form.Text = “Hello,World!”

testVar = “test”

こんな感じで、IronPython側にオブジェクトを渡してIronPython側で操作したり、IronPython側のオブジェクトをC#側で取得することが出来ました。

IronPythonとのオブジェクトのやり取りはScriptScopeオブジェクトを通して行うんですね。

これだけでもかなり面白いです。IronPython1.0からはかなり勝手が変わったようです。

C#|クリップボードビューア作成での悩み

C#でクリップボードの履歴を収集、参照するアプリを作っているんだけど、迷ってます。終了時に収集した履歴をファイルに保存して、起動時に読み込みたいんだけど、その保存形式をどうしようかと。

テキストデータしか収集しない仕様だから保存もプレーンなテキストでやりたいんだけど、区切り文字をどうしようかと悩んでる。クリップボードに格納出来ない文字とかがあればそれに決まりなんだけどなぁ…。

これを書いてるたった今、HTMLエンコードして区切り文字を2chのDATっぽく”><”にするってのを思い付いたんだけど、それもどうかなとも思う…。出来れば保存したファイルをテキストエディタで開いて理解出来る形にしたい。

結局BinaryFormatterで履歴の読み書きを実装しました。しかし、妥協はしたけど納得はしてないので引き続きよい案募集。

機能的にはこれで完成、ClipBoardWatcher(ネーミングセンス皆無w)。

DeskTopMemoに次いで手放せないツールになりそう。

あにすのコード保管庫

今までこっそりと更新してきましたが(mixiとかTwitterには書いてたけど)、大分コード量が増えてきたので告知します。

あにすのコード保管庫

サイドバーのコンテンツにリンクがあります。

よくあるTips集ではなく、コピペしてそのまま使って貰おうという趣旨のオンラインクラスライブラリのようなものです。なのでコードの解説等は殆どありません。コピペで使えるから、中身が分からなくても使い方だけ判ればいいのです。

あれ、これって前にも書いたよな…ってコードをPCの中から探してくるのが面倒になってきたってのがこれを書き始めた動機です。

バグ報告、リクエスト、感想、お小遣い等お待ちしてます。

C#|エクスプローラのコンテキストメニューを表示する

調べていたらなかなか難しそうで悶絶してました。そこで見付けたのがこれ。

http://cspace.s2.xrea.com/software/SimpleCommand/index.php#1000000042

ここのコンテキストメニューってツールです。

これをProcess.Start()メソッドで起動するだけで右クリックメニューを表示出来ます。

これは良いものです。

C#|WPFアプリケーションにてマウスイベントが拾えない問題

まず問題のコード

【XAML】Window1.xaml

<Window x:Class="WpfApplication1.Window1"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    Title="Window1" Height="300" Width="300">

    <Grid MouseLeftButtonDown="Grid_MouseLeftButtonDown">

        

    </Grid>

</Window>

【C#】Window1.xaml.cs

using System.Windows;

using System.Windows.Input;

namespace WpfApplication1 {

    /// <summary>

    /// Window1.xaml の相互作用ロジック

    /// </summary>

    public partial class Window1 :Window {

        public Window1() {

            InitializeComponent();

        }

        private void Grid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) {

            MessageBox.Show("Clicked!");

        }

    }

}

フォームをクリックしても何も起こらない。

しかし、Window1.xamlをこうすると…

【XAML】

<Window x:Class="WpfApplication1.Window1"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    Title="Window1" Height="300" Width="300">

    <Grid MouseLeftButtonDown="Grid_MouseLeftButtonDown" Background="White">

        

    </Grid>

</Window>

GridのBackgroundプロパティにWhiteを指定している。

こうすると意図した通りにメッセージボックスが表示される。

また、Window1.xamlではなくWindow1.xaml.csのコンストラクタで

GridのBackgroundにBrushes.Whiteを指定してもメッセージボックスが表示された。

色が無いコントロールはマウスイベントを拾えない!?仕様なのかバグなのか…。