Pergunta

Eu tenho um problema semelhante ao seguinte mensagem:

Silverlight DataGridTextColumn Binding Visibilidade

Eu preciso ter uma coluna em um Silverlight DataGrid ser visibile / desabou com base em um valor dentro de um ViewModel. Para conseguir isso eu estou tentando vincular a propriedade visibilidade a um ViewModel. No entanto, eu logo descobriu que a propriedade Visibilidade não é um DependencyProperty, portanto, não pode ser vinculado.

Para resolver isso, eu tentei subclasse minha própria DataGridTextColumn. Com esta nova classe, eu criei um DependencyProperty, que em última análise empurra as alterações na propriedade DataGridTextColumn.Visibility. Isso funciona bem, se eu não fizer databind. O momento que eu vincular à minha nova propriedade, ele falhar, com uma exceção 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
}

Aqui está um pequeno trecho do 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>

Um par de fatos importantes.

  • O conversor é de fato definido acima nos recursos locais.
  • O conversor está correta, ele é usado em muitos outros lugares na solução.
  • Se eu substituir o {Binding} sintaxe para a propriedade MyVisibility com "Recolhido" Coluna de facto desaparecer.
  • Se eu criar uma nova DependencyProperty (ou seja, seqüência Foo), e se ligam a ele que eu receber a exceção AG_E_PARSER_BAD_PROPERTY_VALUE também.

Alguém tem alguma idéia de por que isso não está funcionando?

Foi útil?

Solução

Aqui está a solução que eu vim acima com o uso de um pequeno hack.

Primeiro, você precisa para herdar de 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;
        }
    }
}

O DataGridEx classe adiciona um novo DP para colunas esconderijos com base no x: Name de um DataGridColumn e seus descendentes

Para usar em seu 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>

Você precisa adicionar estes a sua ViewModel ou qualquer contexto de dados que você usa.

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"};
}

Para unhide, você só precisa remover o nome correspondente na lista ou recriá-lo sem o nome unhidden.

Outras dicas

Eu tenho uma outra solução para este problema que usa uma abordagem semelhante à propriedade "ligação" que você encontra em DataGridTextColumn. Desde as classes coluna são DependencyObjects, você não pode diretamente vincular a eles, mas se você adicionar uma referência a um FrameworkElement que implementa INotifyPropertyChanged você pode passar uma ligação de dados por meio do elemento, e então usar uma propriedade de dependência para notificar a Coluna que o ligação de dados mudou.

Uma coisa a notar é que ter a ligação no próprio em vez da grade da coluna provavelmente irá significar que você vai querer usar um DataContextProxy para ter acesso ao campo que você quer para se ligar a visibilidade para (ligação a coluna será o padrão para o âmbito da 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"));
            }
        }
    }
}

}

Os herda da coluna DataGrid de DependencyObject vez de FrameworkElement. Em WPF isso não seria grande coisa ... mas no Silverlight só pode vincular a FrameworkElement objetos. Então você começa a mensagem de erro descritiva do AG_E_PARSER_BAD_PROPERTY_VALUE quando você tenta.

Eu não sei o quanto isso vai ajudar, mas eu correr em problema da falta de propriedade de dependência com colunas de grade de dados me no meu mais recente projecto. O que eu fiz para contorná-la, era criar um evento no modelo de exibição em coluna grid, em seguida, quando a grade está sendo montada no cliente, use um fecho para inscrever-se a coluna grade para o modelo de exibição em coluna. Meu problema particular foi de cerca de largura. Ela começa com a classe vista modelo para a coluna de grade, que é algo como isto pseudo-código:

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);
    }
}

Depois, há o código que monta a grade:

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); };
        }
    }
}

Quando o datagrid é redimensionado na aplicação, o evento de redimensionamento é captado e chama o método de redimensionamento na viewmodel a grade está vinculado. Este, por sua vez chama o método de redimensionamento de cada modelo de exibição coluna grid. O modelo de vista coluna de grade, em seguida, dispara o evento ColumnResized, que a coluna de texto grade de dados está inscrito, e de largura é atualizado.

Sei que isso não é resolver diretamente o seu problema, mas foi uma maneira que eu poderia "ligar" um modelo de vista a uma coluna de grade de dados quando não há propriedades de dependência nele. O fechamento é uma construção simples que bem resume o comportamento que eu queria, e é perfeitamente compreensível para alguém que vem ao longo atrás de mim. Eu acho que não é muito difícil de imaginar como poderia ser modificado para lidar com a mudança de visibilidade. Você pode até mesmo ligar o manipulador de eventos-se no evento de carregamento da página de controle / usuário.

Chris Mancini,

você não criar ligação a propriedade "vinculação" de coluna de grade de dados. Bem, você escreve "{user.username Binding}", mas não cria a ligação, porque (como Zachary disse) coluna de datagrid não herda de FrameworkElement e não tem SetBinding método. Assim, a expressão "{Binding user.username}" simplesmente cria objeto Binding e atribuí-la a propriedade de ligação da coluna (esta propriedade é tipo de ligação). Então coluna datagrid enquanto gera conteúdo células (GenerateElement - método protegido) utiliza este objecto de Ligação ao conjunto de ligao sobre os elementos gerados (por exemplo, em textos de propriedade TextBlock gerado) que são FrameworkElements

A solução da GreatTall1 é grande, mas precisa mudar pouco para fazê-lo funcionar.

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"));
}

Note que o problema não é tão simples como 'Visibilidade' não ser uma propriedade de dependência. Em um DataGrid as colunas não são parte da 'árvore' visual para que você não pode usar AncestorType mesmo em WPF (ou Silverlight 5).

Eis alguns links WPF relacionados (por favor, comente se algum destes trabalhos para o Silverlight - desculpe, eu não tenho tempo para teste agora)

Tem um muito bom explicação do problema e falhas de certas soluções (e uma solução inteligente): http://tomlev2.wordpress.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/

perguntas

E um par StackOverflow:

WPF Ocultar DataGridColumn através de um

obrigatório

Binding propriedade Visible de um DataGridColumn no WPF DataGrid

Isso funciona em uma coluna de modelo de grade de dados:

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;
        }
    }
}

A partir da sua classe MyDataGridTextColumn, você poderia obter o DataGrid circundante. Então você começa o seu ViewModel fora do DataContext do DataGrid e adicionar um manipulador para o evento PropertyChanged do seu ViewModel. No manipulador que você acabou de verificar o nome da propriedade e seu valor e alterar a visibilidade da coluna em conformidade. A sua não é muito a melhor solução, mas deve funcionar;)

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top