15

次の投稿のような問題があります。

Silverlight DataGridTextColumn バインディングの可視性

ViewModel 内の値に基づいて、Silverlight DataGrid 内の列を表示/折りたたむ必要があります。これを達成するために、Visibility プロパティを ViewModel にバインドしようとしています。しかし、Visibility プロパティは DependencyProperty ではないため、バインドできないことがすぐにわかりました。

これを解決するために、独自の DataGridTextColumn をサブクラス化しようとしました。この新しいクラスを使用して、最終的に変更を DataGridTextColumn.Visibility プロパティにプッシュする DependencyProperty を作成しました。データバインドしない場合、これはうまく機能します。新しいプロパティにデータバインドした瞬間、AG_E_PARSER_BAD_PROPERTY_VALUE 例外で失敗します。

public class MyDataGridTextColumn : DataGridTextColumn
{
    #region public Visibility MyVisibility

    public static readonly DependencyProperty MyVisibilityProperty =
        DependencyProperty.Register("MyVisibility", typeof(Visibility), typeof(MyDataGridTextColumn), new PropertyMetadata(Visibility.Visible, OnMyVisibilityPropertyChanged));

    private static void OnMyVisibilityPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var @this = d as MyDataGridTextColumn;

        if (@this != null)
        {
            @this.OnMyVisibilityChanged((Visibility)e.OldValue, (Visibility)e.NewValue);
        }
    }

    private void OnMyVisibilityChanged(Visibility oldValue, Visibility newValue)
    {
        Visibility = newValue;
    }

    public Visibility MyVisibility
    {
        get { return (Visibility)GetValue(MyVisibilityProperty); }
        set { SetValue(MyVisibilityProperty, value); }
    }

    #endregion public Visibility MyVisibility
}

XAML の小さなスニペットを次に示します。

<DataGrid ....>
    <DataGrid.Columns>
        <MyDataGridTextColumn Header="User Name"
                              Foreground="#FFFFFFFF"
                              Binding="{Binding User.UserName}"
                              MinWidth="150"
                              CanUserSort="True"
                              CanUserResize="False"
                              CanUserReorder="True"
                              MyVisibility="{Binding Converter={StaticResource BoolToVisibilityConverter}, Path=ShouldShowUser}"/>
        <DataGridTextColumn .../>
    </DataGrid.Columns>
</DataGrid>

いくつかの重要な事実。

  • Converter は、実際には上記のローカル リソースで定義されています。
  • コンバーターは正しく、ソリューションの他の多くの場所で使用されています。
  • MyVisibility プロパティの {Binding} 構文を「Collapsed」に置き換えると、列は実際には消えます。
  • 新しい DependencyProperty (つまり、文字列 Foo) を作成し、それにバインドすると、AG_E_PARSER_BAD_PROPERTY_VALUE 例外も受け取ります。

なぜこれが機能しないのか、誰にもアイデアはありますか?

4

9 に答える 9

7

これが私がちょっとしたハックを使って思いついた解決策です。

まず、DataGridから継承する必要があります。

public class DataGridEx : DataGrid
{
    public IEnumerable<string> HiddenColumns
    {
        get { return (IEnumerable<string>)GetValue(HiddenColumnsProperty); }
        set { SetValue(HiddenColumnsProperty, value); }
    }

    public static readonly DependencyProperty HiddenColumnsProperty =
        DependencyProperty.Register ("HiddenColumns", 
                                     typeof (IEnumerable<string>), 
                                     typeof (DataGridEx),
                                     new PropertyMetadata (HiddenColumnsChanged));

    private static void HiddenColumnsChanged(object sender,
                                             DependencyPropertyChangedEventArgs args)
    {
        var dg = sender as DataGrid;
        if (dg==null || args.NewValue == args.OldValue)
            return;

        var hiddenColumns = (IEnumerable<string>)args.NewValue;
        foreach (var column in dg.Columns)
        {
            if (hiddenColumns.Contains ((string)column.GetValue (NameProperty)))
                column.Visibility = Visibility.Collapsed;
            else
                column.Visibility = Visibility.Visible;
        }
    }
}

DataGridExクラスは、DataGridColumnとその子孫のx:Nameに基づいて列を非表示にするための新しいDPを追加します。

XAMLで使用するには:

<my:DataGridEx x:Name="uiData"
               DataContext="{Binding SomeDataContextFromTheVM}"
               ItemsSource="{Binding Whatever}"
               HiddenColumns="{Binding HiddenColumns}">
    <sdk:DataGridTextColumn x:Name="uiDataCountOfItems">
                            Header="Count"
                            Binding={Binding CountOfItems}"
    </sdk:DataGridTextColumn>
</my:DataGridEx>

これらをViewModelまたは使用するデータコンテキストに追加する必要があります。

private IEnumerable<string> _hiddenColumns;
public IEnumerable<string> HiddenColumns
{
    get { return _hiddenColumns; }
    private set
    {
        if (value == _hiddenColumns)
            return;

        _hiddenColumns = value;
        PropertyChanged (this, new PropertyChangedEventArgs("HiddenColumns"));
    }
}

public void SomeWhereInYourCode ()
{
    HiddenColumns = new List<string> {"uiDataCountOfItems"};
}

再表示するには、対応する名前をリストから削除するか、非表示の名前なしで再作成するだけです。

于 2010-09-20T17:50:15.060 に答える
6

DataGridTextColumn にある "Binding" プロパティと同様のアプローチを使用して、この問題を解決する別の方法があります。列クラスは DependencyObjects であるため、それらに直接データバインドすることはできませんが、INotifyPropertyChanged を実装する FrameworkElement への参照を追加すると、要素にデータバインディングを渡し、依存関係プロパティを使用して列に通知することができます。データバインディングが変更されました。

注意すべきことの 1 つは、Grid ではなく Column 自体にバインディングを設定するということは、おそらくDataContextProxyを使用して、Visibility をバインドするフィールドにアクセスする必要があるということです (列バインディングはデフォルトでItemSource のスコープ)。

using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace XYZ.Controls
{
public class ExtendedDataGridTextColumn : DataGridTextColumn
{
    private readonly Notifier _e;

    private Binding _visibilityBinding;
    public Binding VisibilityBinding
    {
        get { return _visibilityBinding; }
        set
        {
            _visibilityBinding = value;
            _e.SetBinding(Notifier.MyVisibilityProperty, _visibilityBinding);
        }
    }

    public ExtendedDataGridTextColumn()
    {
        _e = new Notifier();
        _e.PropertyChanged += ToggleVisibility;
    }

    private void ToggleVisibility(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == "Visibility")
            this.Visibility = _e.MyVisibility;
    }

    //Notifier class is just used to pass the property changed event back to the column container Dependency Object, leaving it as a private inner class for now
    private class Notifier : FrameworkElement, INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public Visibility MyVisibility
        {
            get { return (Visibility)GetValue(MyVisibilityProperty); }
            private set { SetValue(MyVisibilityProperty, value); }
        }

        public static readonly DependencyProperty MyVisibilityProperty = DependencyProperty.Register("MyVisibility", typeof(Visibility), typeof(Notifier), new PropertyMetadata(MyVisibilityChanged));

        private static void MyVisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var n = d as Notifier;
            if (n != null)
            {
                n.MyVisibility = (Visibility) e.NewValue;
                n.PropertyChanged(n, new PropertyChangedEventArgs("Visibility"));
            }
        }
    }
}

}

于 2010-10-20T01:05:42.820 に答える
2

これがどれだけ役立つかはわかりませんが、最新のプロジェクトで、データグリッド列に関する依存関係プロパティの欠如の問題に遭遇しました。それを回避するために私がしたことは、グリッド列ビューモデルでイベントを作成し、グリッドがクライアントでアセンブルされているときに、クロージャーを使用してグリッド列を列ビューモデルにサブスクライブすることでした。私の特定の問題は幅の周りでした。これは、グリッド列のビューモデルクラスから始まります。これは、次の擬似コードのようになります。

public delegate void ColumnResizedEvent(double width);

public class GridColumnViewModel : ViewModelBase
{
    public event ColumnResizedEvent ColumnResized;

    public void Resize(double newContainerWidth)
    {
        // some crazy custom sizing calculations -- don't ask...
        ResizeColumn(newWidth);
    }

    public void ResizeColumn(double width)
    {
        var handler = ColumnResized;
        if (handler != null)
            handler(width);
    }
}

次に、グリッドをアセンブルするコードがあります。

public class CustomGrid
{
    public CustomGrid(GridViewModel viewModel)
    {
        // some stuff that parses control metadata out of the view model.
        // viewModel.Columns is a collection of GridColumnViewModels from above.
        foreach(var column in viewModel.Columns)
        {
            var gridCol = new DataGridTextColumn( ... );
            column.ColumnResized  += delegate(double width) { gridCol.Width = new DataGridLength(width); };
        }
    }
}

アプリケーションでデータグリッドのサイズが変更されると、サイズ変更イベントが取得され、グリッドがバインドされているビューモデルでサイズ変更メソッドが呼び出されます。これにより、各グリッド列ビューモデルのサイズ変更メソッドが呼び出されます。次に、グリッド列ビューモデルはColumnResized、データグリッドテキスト列がサブスクライブされているイベントを発生させ、その幅が更新されます。

これは問題を直接解決するものではないことはわかっていますが、依存関係のプロパティがない場合に、ビューモデルをデータグリッド列に「バインド」する方法でした。クロージャーは、私が望んでいた動作をうまくカプセル化した単純な構造であり、私の後ろにやってくる誰かにとって非常に理解しやすいものです。視界の変化に対応するためにどのように修正できるか想像するのはそれほど難しいことではないと思います。ページ/ユーザーコントロールのロードイベントにイベントハンドラーを接続することもできます。

于 2009-07-15T07:57:58.263 に答える
1

これは、データ グリッド テンプレートの列で機能します。

public class ExtendedDataGridColumn : DataGridTemplateColumn
{
    public static readonly DependencyProperty VisibilityProperty = DependencyProperty.Register("Visibility", typeof(Visibility), typeof(DataGridTemplateColumn), new PropertyMetadata(Visibility.Visible, VisibilityChanged));
    public new Visibility Visibility
    {
        get { return (Visibility)GetValue(VisibilityProperty); }
        set { SetValue(VisibilityProperty, value); }
    }
    private static void VisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if ((DataGridTemplateColumn)d != null)
        {
            ((DataGridTemplateColumn)d).Visibility = (Visibility)e.NewValue;
        }
    }
}
于 2012-03-21T20:00:03.800 に答える
1

クリス・マンシーニ

データグリッド列の「Binding」プロパティへのバインディングは作成しません。さて、「{Binding User.UserName}」と書きますが、(zachary が言ったように) datagrid 列は FrameworkElement から継承されず、SetBinding メソッドがないため、バインディングは作成されません。したがって、式 "{Binding User.UserName}" は単純に Binding オブジェクトを作成し、それを列の Binding プロパティに割り当てます (このプロパティは Binding の型です)。次に、セルのコンテンツを生成する間のデータグリッド列 (GenerateElement - 保護されたメソッド) は、この Binding オブジェクトを使用して、FrameworkElements である生成された要素 (生成された TextBlock の Text プロパティなど) にバインディングを設定します。

于 2009-07-09T10:04:31.670 に答える
1

問題は、「可視性」が依存関係プロパティではないという単純なものではないことに注意してください。DataGrid では、列は視覚的な「ツリー」の一部ではないため、WPF (または Silverlight 5) でも AncestorType を使用できません。

ここにいくつかの WPF 関連リンクがあります (これらのいずれかが Silverlight で機能する場合はコメントしてください。申し訳ありませんが、今はテストする時間がありません)。

問題と特定の解決策の失敗 (および巧妙な解決策) の非常に優れた説明があります: http://tomlev2.wordpress.com/2011/03/21/wpf-how-to-bind-to-data-when- The-datacontext-is-not-inherited/

そしていくつかの StackOverflow の質問:

WPF バインディングを介して DataGridColumn を非表示にする

WPF DataGrid の DataGridColumn の Visible プロパティのバインディング

于 2012-02-23T01:53:00.590 に答える
1

GreatTall1 のソリューションは優れていますが、機能させるには少し変更する必要があります。

var n = d as Notifier;
if (n != null)
{
     //Assign value in the callback will break the binding.
     //n.MyVisibility = (Visibility)e.NewValue;
     n.PropertyChanged(n, new PropertyChangedEventArgs("Visibility"));
}
于 2011-01-13T05:39:48.780 に答える
0

MyDataGridTextColumn クラスから、周囲の DataGrid を取得できます。次に、DataGrid の DataContext から ViewModel を取得し、ViewModel の PropertyChanged イベントにハンドラーを追加します。ハンドラーでは、プロパティ名とその値を確認し、それに応じて列の可視性を変更するだけです。最善の解決策ではありませんが、うまくいくはずです;)

于 2009-06-27T15:50:31.163 に答える