< December 2008 | January 2009 | February 2009 >

January 28, 2009

NULL 許容型 (Nullable)

null 許容型って、間違えやすいけど参照型になるわけじゃないんだ。値型だけど null 値を持ちうるようになるだけ。

だからこんなプログラムの実行結果は…

struct Data
{
    public string str;
    public int number;
}

class Program
{
    static void Main (string [] args)
    {
        Data data1;
        Data? data2 = null;

        data1.number = 1;
        data1.str = "最初の値";

        data2 = data1;
        Console.WriteLine ("{0}:{1}", data2.Value.number, data2.Value.str);

        data1.number = 2;
        data1.str = "値を変更してみた";

        Console.WriteLine ("{0}:{1}", data2.Value.number, data2.Value.str);
    }
}

こうなる。

1:最初の値
1:最初の値
続行するには何かキーを押してください . . .

それと、null を代入しても、おんなじだけのスタックを消費します。値型だから。

連番のプロパティを持つ Items を Grid 上に整列させるコンバータ

public class NumberGridConverter: DependencyObject, IValueConverter
{
    /// <summary>
    /// <c>BaseNumber</c> 依存関係プロパティ
    /// </summary>
    /// <value><c>NumberGridConverter.BaseNumber</c> 属性値</value>
    public int BaseNumber
    {
        get { return (int) GetValue (BaseNumberProperty); }
        set { SetValue (BaseNumberProperty, value); }
    }

    /// <summary><c>BaseNumber</c> 依存関係プロパティを識別します。</summary>
    public static readonly DependencyProperty BaseNumberProperty =
        DependencyProperty.Register ("BaseNumber", typeof (int), typeof (NumberGridConverter), new FrameworkPropertyMetadata ((int) 0, FrameworkPropertyMetadataOptions.AffectsRender));

    /// <summary>
    /// <c>Columns</c> 依存関係プロパティ
    /// </summary>
    /// <value><c>NumberGridConverter.Columns</c> 属性値</value>
    public int Columns
    {
        get { return (int) GetValue (ColumnsProperty); }
        set { SetValue (ColumnsProperty, value); }
    }

    /// <summary><c>Columns</c> 依存関係プロパティを識別します。</summary>
    public static readonly DependencyProperty ColumnsProperty =
        DependencyProperty.Register ("Columns", typeof (int), typeof (NumberGridConverter), new FrameworkPropertyMetadata ((int) 1, FrameworkPropertyMetadataOptions.AffectsRender));

    /// <summary>
    /// <c>Rows</c> 依存関係プロパティ
    /// </summary>
    /// <value><c>NumberGridConverter.Rows</c> 属性値</value>
    public int Rows
    {
        get { return (int) GetValue (RowsProperty); }
        set { SetValue (RowsProperty, value); }
    }

    /// <summary><c>Rows</c> 依存関係プロパティを識別します。</summary>
    public static readonly DependencyProperty RowsProperty =
        DependencyProperty.Register ("Rows", typeof (int), typeof (NumberGridConverter), new FrameworkPropertyMetadata ((int) 1, FrameworkPropertyMetadataOptions.AffectsRender));

    /// <summary>
    /// <c>Orientation</c> 依存関係プロパティ
    /// </summary>
    /// <value><c>NumberGridConverter.Orientation</c> 属性値</value>
    public System.Windows.Controls.Orientation Orientation
    {
        get { return (System.Windows.Controls.Orientation) GetValue (OrientationProperty); }
        set { SetValue (OrientationProperty, value); }
    }

    /// <summary><c>Orientation</c> 依存関係プロパティを識別します。</summary>
    public static readonly DependencyProperty OrientationProperty =
        DependencyProperty.Register ("Orientation", typeof (System.Windows.Controls.Orientation), typeof (NumberGridConverter), new FrameworkPropertyMetadata (System.Windows.Controls.Orientation.Horizontal, FrameworkPropertyMetadataOptions.AffectsRender));


    #region IValueConverter メンバ

    public object Convert (object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        var param = parameter as string;
        var number = (int) value - this.BaseNumber;
        if (param != null)
        {
            switch (param.ToUpper ())
            {
                case "COLUMN":
                    switch (this.Orientation)
                    {
                        case System.Windows.Controls.Orientation.Horizontal:
                            return (number % this.Columns);
                        case System.Windows.Controls.Orientation.Vertical:
                            return (number / this.Rows);
                        default:
                            break;
                    }
                    break;

                case "ROW":
                    switch (this.Orientation)
                    {
                        case System.Windows.Controls.Orientation.Horizontal:
                            return (number / this.Columns);
                        case System.Windows.Controls.Orientation.Vertical:
                            return (number % this.Rows);
                        default:
                            break;
                    }
                    break;

                default:
                    break;
            }
        }
        return DependencyProperty.UnsetValue;
    }

    public object ConvertBack (object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException ();
    }

    #endregion
}

Microsoft SQL Server Express の認証モード

以前に MSDEで統合Windows認証<->SQLServer認証を切り替える というのを書きましたが、SQL Server Express では以下のレジストリ位置になるみたいです。

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL.1\MSSQLServer]
"LoginMode"=dword:00000002

レジストリの値は変わらないみたい。

認証の種類
統合Windows認証 dword:00000001
SQL Server認証 dword:00000002

あと、osql から接続する場合は、[SQL Server 2005 セキュリティ構成] を起動して、[サービスと接続のセキュリティ構成] より、リモート接続を有効にする必要があるみたいです。

January 27, 2009

Binding の StringFormat プロパティのカルチャって ?

String.Format () でカルチャに依存する書式を指定した場合、カルチャを指定しないとそのスレッドのカルチャ情報が使用されるらしいです。具体的には System.Threading.Thread.CurrentThread.CurrentCulture です。

が、BindingBase の StringFormat で指定した場合、カレントスレッドのカルチャ情報と異なるように見えますがー。なんか私間違ってますかー ? 何か指定がいるのー ?

Binding の結果
String.Format の結果

<Window
    x:Class="SampleApp.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:sys="clr-namespace:System;assembly=mscorlib"
    xmlns:th="clr-namespace:System.Threading;assembly=mscorlib"
    Title="Binding の StringFormat のカルチャって?"
    SizeToContent="WidthAndHeight">
    <StackPanel Grid.IsSharedSizeScope="True">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" SharedSizeGroup="Caption"/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <TextBlock Grid.Column="0" Margin="5" Text="StringFormat なし"/>
            <TextBlock Grid.Column="1" Margin="5" Text="{Binding Source={x:Static sys:DateTime.Now}}"/>
        </Grid>
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" SharedSizeGroup="Caption"/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <TextBlock Grid.Column="0" Margin="5" Text="StringFormat あり"/>
            <TextBlock Grid.Column="1" Margin="5" Text="{Binding Source={x:Static sys:DateTime.Now}, StringFormat={}{0:F}}"/>
        </Grid>
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" SharedSizeGroup="Caption"/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <TextBlock Grid.Column="0" Margin="5" Text="CurrentUICulture の設定"/>
            <TextBlock Grid.Column="1" Margin="5" Text="{Binding Source={x:Static th:Thread.CurrentThread}, Path=CurrentUICulture.DateTimeFormat.FullDateTimePattern}"/>
        </Grid>
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" SharedSizeGroup="Caption"/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <TextBlock Grid.Column="0" Margin="5" Text="CurrentCulture の設定"/>
            <TextBlock Grid.Column="1" Margin="5" Text="{Binding Source={x:Static th:Thread.CurrentThread}, Path=CurrentCulture.DateTimeFormat.FullDateTimePattern}"/>
        </Grid>
        <Button Margin="5" Click="Button_Click">String.Format の結果</Button>
    </StackPanel>
</Window>
namespace SampleApp
{
    /// <summary>
    /// Window1.xaml の相互作用ロジック
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1 ()
        {
            InitializeComponent ();
        }
 
        private void Button_Click (object sender, RoutedEventArgs e)
        {
            MessageBox.Show (String.Format ("{0:F}", System.DateTime.Now));
        }
    }
}

January 23, 2009

続:最大化ボタン・最小化ボタン・システムメニューのないウインドウ

最大化ボタン・最小化ボタン・システムメニューのないウインドウの実装を考え中。

これもありか?

using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;

namespace WindowStyleProject { public abstract class StyledWindow : Window { const int GWL_STYLE = -16;
/// <summary> /// ウインドウスタイルを上書きする /// 継承先でオーバーライドする /// </summary> /// <param name="windowStyle">現在のウインドウスタイル</param> /// <returns>新たに設定するウインドウスタイル</returns> protected virtual WindowStyleEnum OverrideWindowStyle (WindowStyleEnum windowStyle) { return windowStyle; // デフォルトは変更なし }
/// <summary> /// ウインドウスタイルを設定する /// </summary> /// <param name="e"></param> protected override void OnSourceInitialized (EventArgs e) { base.OnSourceInitialized (e);
// 現在のウインドウスタイルを取得 IntPtr handle = (new WindowInteropHelper (this)).Handle; var originalWindowStyle = (WindowStyleEnum) GetWindowLong (handle, GWL_STYLE);
// 継承先で更新された値を取得 var newWindowStyle = this.OverrideWindowStyle (originalWindowStyle);
// 上書き if (originalWindowStyle != newWindowStyle) SetWindowLong (handle, GWL_STYLE, (uint) newWindowStyle); }
[DllImport ("user32.dll")] static extern uint GetWindowLong (IntPtr hWnd, int nIndex);
[DllImport ("user32.dll")] static extern uint SetWindowLong (IntPtr hWnd, int nIndex, uint dwNewLong);
}
public enum WindowStyleEnum : uint { // // Window Styles // WS_OVERLAPPED = 0x00000000, WS_POPUP = 0x80000000, WS_CHILD = 0x40000000, WS_MINIMIZE = 0x20000000, WS_VISIBLE = 0x10000000, WS_DISABLED = 0x08000000, WS_CLIPSIBLINGS = 0x04000000, WS_CLIPCHILDREN = 0x02000000, WS_MAXIMIZE = 0x01000000, WS_CAPTION = 0x00C00000, // WS_BORDER | WS_DLGFRAME WS_BORDER = 0x00800000, WS_DLGFRAME = 0x00400000, WS_VSCROLL = 0x00200000, WS_HSCROLL = 0x00100000, WS_SYSMENU = 0x00080000, WS_THICKFRAME = 0x00040000, WS_GROUP = 0x00020000, WS_TABSTOP = 0x00010000,
WS_MINIMIZEBOX = 0x00020000, WS_MAXIMIZEBOX = 0x00010000,
WS_TILED = WS_OVERLAPPED, WS_ICONIC = WS_MINIMIZE, WS_SIZEBOX = WS_THICKFRAME, WS_TILEDWINDOW = WS_OVERLAPPEDWINDOW,
// // Common Window Styles // WS_OVERLAPPEDWINDOW = (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX),
WS_POPUPWINDOW = (WS_POPUP | WS_BORDER | WS_SYSMENU),
WS_CHILDWINDOW = (WS_CHILD), }
}

使い方は、StyledWindow を継承して、OverrideWindowStyle をオーバーライドする。

// Window1.xaml
namespace WindowStyleProject
{
    /// <summary>
    /// Window1.xaml の相互作用ロジック
    /// </summary>
    public partial class Window1 : StyledWindow
    {
        public Window1 ()
        {
            InitializeComponent ();
        }

protected override WindowStyleEnum OverrideWindowStyle (WindowStyleEnum windowStyle) { return windowStyle & (~WindowStyleEnum.WS_SYSMENU); } } }

あーでも、XAML のほうも書き換えなきゃな。これがうざい。

<local:StyledWindow x:Class="WindowStyleProject.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WindowStyleProject"
    Title="Window1" Height="300" Width="300">
    <Grid>
        <!-- 中略 -->
    </Grid>
</local:StyledWindow>

January 22, 2009

開くダイアログ

ファイルを開くダイアログの WPF 版は、Microsoft.Win32.OpenFileDialog クラス

private void Button_Click (object sender, RoutedEventArgs e)
{
    var openFileDialog = new Microsoft.Win32.OpenFileDialog ();
    openFileDialog.Filter = "C# ソースファイル|*.cs|すべてのファイル|*.*";
    openFileDialog.InitialDirectory = System.Environment.GetFolderPath (Environment.SpecialFolder.MyDocuments);
    openFileDialog.CheckFileExists = true;
    openFileDialog.Multiselect = true;
    openFileDialog.ShowReadOnly = false;
    openFileDialog.Title = "ソースファイルの選択";
    var result= openFileDialog.ShowDialog ();
    if (result ?? false)
    {
        MessageBox.Show (openFileDialog.FileNames.Aggregate ((str, nextstr) => str + "\n" + nextstr));
    }
}

上の例は複数ファイルを選択できるダイアログを表示するが、選択された結果は FileNames プロパティより string[] 型として取得できる。ちゃんとフルパス。でも、このダイアログは Vista で実行しても XP スタイルになっちゃうらしい (未確認)

保存するほうは、同様に SaveFileDialog クラスというのがある。

プロセスを起動する

プロセスを起動する方法を調べた。System.Diagnostics.Process クラスの Start メソッドで起動できるようだ。返されるのは Process クラスのインスタンス。WaitForExit メソッドで終了待機も可能。

private void Button_Click (object sender, RoutedEventArgs e)
{
    var process = System.Diagnostics.Process.Start ("notepad.exe");
    process.WaitForExit ();
    MessageBox.Show ("終了しました");
}

WaitForExit は完全にブロッキングして待機するが、EnableRaisingEvents プロパティを設定するとイベントを発火するようになる。GUI スレッドから起動する場合はこっちのがいいな。

private void Button_Click (object sender, RoutedEventArgs e)
{
    var process = System.Diagnostics.Process.Start ("notepad.exe");
    process.EnableRaisingEvents = true;
    process.Exited += new EventHandler (NotepadProcess_Exited);
}
void NotepadProcess_Exited (object sender, EventArgs e) { MessageBox.Show ("終了しました"); }

January 20, 2009

メモ: PropertyPath の XAML 構文

PropertyPath の XAML 構文

ちゃんとドキュメントあるやん !

January 16, 2009

最大化ボタン・最小化ボタン・システムメニューのないウインドウ

WPF で、最大化ボタン・最小化ボタン・システムメニュー (閉じるボタン) のないウインドウをつくった。ゴリゴリ書くしかないよね、多分。

Window1.windowstyle.cs とか言うファイル名でソースを追加 (隠しておきたいし) して、こんな記述。

using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
namespace MyProject { public partial class Window1 : Window { const int GWL_STYLE = -16; const int WS_SYSMENU = 0x00080000;
protected override void OnSourceInitialized (EventArgs e) { base.OnSourceInitialized (e); IntPtr handle = (new WindowInteropHelper (this)).Handle; var style = GetWindowLong (handle, GWL_STYLE); style &= (~WS_SYSMENU); SetWindowLong (handle, GWL_STYLE, style); }
[DllImport ("user32.dll")] static extern int GetWindowLong (IntPtr hWnd, int nIndex);
[DllImport ("user32.dll")] static extern int SetWindowLong (IntPtr hWnd, int nIndex, int dwNewLong); } }

みっともないけどこれも仕事 !

スタイルの自己参照

別に処理を書いてない Window の生成時に StackOverflowException が表示されるようになってしまった。

コールスタックは表示できず、原因を突き止めるのが大変。

あちこちコメントアウトしたりして犯人を追い込んだところ、コイツでした。

<Style TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
    <!-- 省略... -->
</Style>

BaseOn で自己を参照しようとして、再帰に陥ってたみたい。うーん。

January 9, 2009

文字列リテラルを複数行で記述する

C の場合、隣接した文字列リテラルはくっつける使用だったため、二重引用符で括った文字列を複数行に並べて行末文字を \ でエスケープすることによって文字列リテラルを複数行で定義したりした。
# 私は \ エスケープを使用せず、定義全体を丸括弧で括ってたけどw

C# の場合は逐語的文字列というのがある。二重引用符の前に @ を付けて定義する。

参照: 文字列の基本 (C# プログラミング ガイド)

逐語的文字列は、文字列テキストの一部として改行文字を保持するため、複数行文字列の初期化に使用できます。引用符を逐語的文字列に埋め込むには、二重の引用符を使用します。

January 8, 2009

添付プロパティにしちゃえばいいんじゃない ?

ListView のソート実装の件。詳細はまた書きます。

public class GridViewSortHelper
{
    protected ListView  listView;
    protected GridViewColumnHeader lastSortedHeader;
    protected ListSortDirection lastSortDirection = ListSortDirection.Ascending;

    /// <summary>
    /// <c>GridViewSortHelper.SortKey</c> 添付プロパティ
    /// </summary>
    /// <value>ソートに使用するキー文字列</value>
    public static readonly DependencyProperty SortKeyProperty =
        DependencyProperty.RegisterAttached ("SortKey", typeof (string), typeof (GridViewSortHelper), new FrameworkPropertyMetadata (""));

    /// <summary>
    /// 要素に <c>GridViewSortHelper.SortKey</c> 添付プロパティを設定します。
    /// </summary>
    /// <param name="element">プロパティ値の書き込み対象の要素。</param>
    /// <param name="value">要素の <c>GridViewSortHelper.SortKey</c> 属性値</param>
    public static void SetSortKey (GridViewColumn element, string value)
    {
        element.SetValue (SortKeyProperty, value);
    }

    /// <summary>
    /// 要素の <c>GridViewSortHelper.SortKey</c> 添付プロパティを取得します
    /// </summary>
    /// <param name="element"> プロパティ値の読み取り元の要素。</param>
    /// <returns>要素の <c>GridViewSortHelper.SortKey</c> 属性値</returns>
    public static string GetSortKey (GridViewColumn element)
    {
        return (string) element.GetValue (SortKeyProperty);
    }

    /// <summary>
    /// コンストラクタ
    /// </summary>
    /// <param name="listView">ソート対象の ListView</param>
    public GridViewSortHelper (ListView listView)
    {
        this.listView = listView;
    }

    /// <summary>
    /// ヘッダクリック時処理
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    public void OnHeaderClicked (object sender, RoutedEventArgs e)
    {
        var headerClicked = e.OriginalSource as GridViewColumnHeader;
        if (headerClicked != null)
        {
            var direction = ListSortDirection.Ascending;
            if (headerClicked == lastSortedHeader)
            {
                if (lastSortDirection == ListSortDirection.Ascending)
                {
                    direction = ListSortDirection.Descending;
                }
            }
            var key = GetSortKey (headerClicked.Column);

            var dataView = CollectionViewSource.GetDefaultView (this.listView.ItemsSource);
            dataView.SortDescriptions.Clear ();
            var sortDescription = new SortDescription (key, direction);
            dataView.SortDescriptions.Add (sortDescription);
            dataView.Refresh ();

            lastSortedHeader = headerClicked;
            lastSortDirection = direction;
        }

    }
}

<elem xmlns=''> は指定できません。

XmlSerializer でクラスの配列をシリアライズしたものをデシリアライズしようとしたら InvalidOperationException が発生してしまった。書いたコードは大雑把にはこんな感じ

public class Entry
{    
    // メンバは省略

    /// <summary>
    /// Xml より Entry の配列を読み込むメソッド
    /// </summary>
    /// <param name="path">Xml ファイルのパス</param>
    /// <returns>読み込んだ Entry 型の配列</returns>
    public static Entry [] LoadEntries (string path)
    {
        var serializer = new System.Xml.Serialization.XmlSerializer (typeof (Entry []), new System.Xml.Serialization.XmlRootAttribute ("Entries"));
        var stream = new System.IO.StreamReader (path, Encoding.Default);
        var entries = serializer.Deserialize (stream) as Entry [];
        stream.Close ();
        return entries;
    }

    static void Main (string [] args)
    {
        var entries = LoadEntries ("c:\\test.xml");
    }
}

Exception の Message は「XML ドキュメント (2,2) でエラーが発生しました。」、さらに InnerException の Message は「<elem xmlns=''> は指定できません。」読み込もうとした XML ファイルを開いてみると、確かにデフォルトの名前空間は指定されていない。あれー ? でもこれ、同じクラスをシリアライズして生成したファイルなんだけど…。

しばし悩んでたけど、原因は結局こちら。

var serializer = new System.Xml.Serialization.XmlSerializer (typeof (Entry []), new System.Xml.Serialization.XmlRootAttribute ("Entries"));

ルート要素の名前を指定しているが、ファイル内の実際のルート要素と違っちゃってた。ただそれだけだった。

ちなみに、対応する英語の Message は「 There is an error in XML document (2, 2). 」と「<elem xmlns=''> was not expected. 」だと思われる。