2008/2/11 Mon

WPFのListViewのGrid表示モードでの右寄せとか(2)

Filed under: プログラム — nico @ 21:23:49

前回の続き。
とりあえず、XAMLだけで実現可能なListViewのGrid表示モードの右寄せ等の仕方を紹介したわけですが、ちょっと漏れがあったのでまず、その話から。

今回のもそうですが、結局やってる事は対応するカラムのGridViewColumnを見つけてきて、そのActualWidthを元にTextBlockの幅を決定という作業なんですが、その副作用として、カラムセパレータのダブルクリックでの自動幅調整機能が使えなくなってたり。
なぜか取得元のパラメータをActualWidthからWidthにすると出来るようにもなるけど、その場合の欠点として自動調整直後はTextBlockの幅も最小幅になってて左寄せ状態に戻る現象が発生したりします。
今は対策方法が不明なので、ActualWidthにして自動調整諦めてる状況です。

てことで、本題。
C#も使って対処(VBでもできると思うけど、俺VB嫌いなんだよね・・・)する方法。
方法論としては、コンバーター使ったりとか色々あるんだけど、ぶっちゃけ一気に飛ばしてカスタムコントロールで実装する方式。

問題のGridViewColumnを検索する方法は、ちょと泥臭くVisualTreeを辿る羽目に。
VisualTreeとしては、途中のごちゃごちゃしたのを省略すると、以下のようになってるので、それを順に辿る方式で。

・GridView
+ GridViewHeaderRowPresenter
- + GridViewColumnHeader
・・・
- + GridViewRowPresenter
- - + TextBlock
 または
- - + ContentPresenter
- - - + TextBlock

間にContentPresenterが入るパターンは、GridViewColumnにCellTemplateを指定して、各セルのテンプレートデザインを使用した場合。
問題の端が切れるMarginはここに指定されてる。
CellTemplate指定しなかった場合は、直接TextBlockに指定されてるとは思うけど、未確認。

てことで、以上を踏まえてソースなぞ。

C#
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;

namespace GridViewStretchPanel
{
    // GridViewのカラム幅いっぱいに広がるコンテナクラス
    public class GridViewStretchPanel : ContentControl
    {
        // メタデータ定義
        static GridViewStretchPanel()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(GridViewStretchPanel), new FrameworkPropertyMetadata(typeof(GridViewStretchPanel)));
        }

        // コンストラクタ
        public GridViewStretchPanel()
        {
        }

        // コントロール初期化終了処理
        public override void EndInit()
        {
            // ContentControl.EndInit()呼び出し
            base.EndInit();

            double padding = 0; // サイズから除去する余白

            // GridViewRowPresenterを検索
            DependencyObject prev = this;
            DependencyObject gridRow;
            for (gridRow = this; gridRow != null; gridRow = VisualTreeHelper.GetParent(prev))
            {
                if (gridRow.GetType() == typeof(GridViewRowPresenter))
                {
                    break;
                }
                // Marginプロパティの値を余白幅として保存
                Object elmPadding = gridRow.GetValue(FrameworkElement.MarginProperty);
                if (elmPadding != null)
                {
                    Thickness t = (Thickness)elmPadding;
                    padding += t.Left + t.Right;
                }
                prev = gridRow;
            }
            if (gridRow == null)
            {
                // 親かGridViewではない、またはXAMLデザインモード
                return;
            }
            // VisualTreeで見つかる何番目の子要素かでカラムのIndexを取得
            int column;
            int max = VisualTreeHelper.GetChildrenCount(gridRow);
            for (column = 0; (column < max) && (VisualTreeHelper.GetChild(gridRow, column) != prev); column++)
            {
            }

            // 直接幅を設定してしまうと変更時反映されないので、バインディング作成
            GridViewRowPresenter row = (GridViewRowPresenter)gridRow;
            Binding b = new Binding("ActualWidth");
            b.Source = row.Columns[column];
            b.Converter = new RemoveMarginConverter(padding);
            SetBinding(FrameworkElement.WidthProperty, b);
        }

        // 余白を除去して幅を設定するコンバータ
        internal class RemoveMarginConverter : IValueConverter
        {
            double padding;
            internal RemoveMarginConverter(double padding)
            {
                this.padding = padding;
            }
            internal RemoveMarginConverter()
            {
                this.padding = 0;
            }

            public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                if (value.GetType() != typeof(Double))
                {
                    // デザインモード時はDouble以外の型??
                    return value;
                }
                double width = (double)value;
                width -= padding;
                return width<0 ? 0 : width;
            }

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

カスタムコントロールのUIデザインを定義しなければならないので、以下のスタイルをThemes/Generic.xamlか貼り付けるWindowのリソースブロックにでも追加。
(xmlnsは適時処理する)

XAML
<Style TargetType="{x:Type local:GridViewStretchPanel}">
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="{x:Type local:GridViewStretchPanel}">
        <ContentPresenter/>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

ふぅ、なげーよ<俺
最後に使う場所で以下のようにすればOK。

XAML
<DataTemplate x:Key="myTemplate">
    <local:GridViewStretchPanel>
        <TextBlock ・・・ />
    </local:GridViewStretchPanel>
</DataTemplate>
  ・・・
<GridViewColumn CellTemplate="{StaticResource myTemplate}" ・・・/>

てなことで、以上
一応ContentControl使ったカスタムコントロールの作成の実験も兼ねられたのでいい経験にはなったけど、激しく面倒w

カスタムコントロールライブラリでの提供もちょっと考えたけど、これだけの為にDLL一個増えるのもなぁって考えてヤメタw
カスタムコントロールのUIデザインを記述するXAMLをクラスファイルの方から指定とか出来ればXAMLとcsだけ提供ってできるんだけど、そーゆーのねーのかな・・・。

(2/19 追記)
通りすがりさん情報でもっと楽にできる事が判明。
詳しくはこちらへ。

コメント (0) »

この記事にはまだコメントがついていません。

コメント RSS トラックバック URL

コメントをどうぞ

Link Free. Copyright (C) 2005-2007 nico. All rights reserved.
HTML convert time: 0.669 sec. Powered by WordPress ME