简体   繁体   中英

WPF DataGrid, customizing the display with a converter

I have a unique situation. In one class, I have an inner class that acts as pretty much just a "display" class. In the outer class there is a method called GetDisplayObject that returns a type of the inner class.

I'm trying to bind to a datagrid with the outer class, but by using a converter I'd like to get the correct display. This way I won't have to change a bunch of code in our application and just add a few lines in a couple .xaml files.

I made a little test app that pretty much sums up my problem at it's most basic level. Ideally I'd like to solve the problem by using a converter and returning values only as a display, that way when i'm using the SelectedItem I won't have to change a ton of code that is depending on that certain type(In this case would be the DataObject type).

So here is the objects i'm stuck dealing with

namespace TestApp
{
    public class DataObject
    {
        public class DataObjectDisplay
        {
            public string ObjectDisplay { get; set; }
        }

        // props
        public int Id { get; set; }
        public object Object1 { get; set; }
        public object Object2 { get; set; }

        // method for getting the display
        public DataObjectDisplay GetDisplayObject()
        {
            DataObjectDisplay display = new DataObjectDisplay();

            // logic for determining which object should be displayed
            if(Object1 == null)
            {
                display.ObjectDisplay = "Object1";
            }
            else
            {
                display.ObjectDisplay = "Object2";
            }
            return display; 
        }
    }
}

Here is the Code Behind of my xaml

using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;

namespace TestApp
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
    public MainWindow()
    {
        this.DataContext = this;
        InitializeComponent();

        this.DataObjectCollection = new ObservableCollection<DataObject>();
        this.DataObjectCollection.Add(new DataObject() { Id = 1, Object1 = "this", Object2 = "that" });
        this.DataObjectCollection.Add(new DataObject() { Id = 1, Object2 = "that" });

        this.SelectedItem = new DataObject();
    }

    private ObservableCollection<DataObject> dataObjectCollection;
    private DataObject selectedItem;

    public ObservableCollection<DataObject> DataObjectCollection
    {
        get { return this.dataObjectCollection; }
        set
        {
            dataObjectCollection = value;
            OnNotifyPropertyChanged("DataObjectCollection");
        }
    } 

    public DataObject SelectedItem
    {
        get { return this.selectedItem; }
        set
        {
            selectedItem = value;
            OnNotifyPropertyChanged("SelectedItem");
        }
    }


    public event PropertyChangedEventHandler PropertyChanged;
    private void OnNotifyPropertyChanged(string property = "")
    {
        if(PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(property));
        }
    }
}
}

Here is the xaml(This is something like what i'd want to do, using the itemtemplate or something similar, and then a converter to call this GetDisplay function)

<Window x:Class="TestApp.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:TestApp"
    mc:Ignorable="d"
    Title="MainWindow" Height="350" Width="525">
<Window.Resources>
    <local:DataObjectToDisplayDataObjectConverter x:Key="ToDisplayConverter"/>
</Window.Resources>
<StackPanel>
    <DataGrid ItemsSource="{Binding DataObjectCollection}" SelectedItem="{Binding SelectedItem}" MinHeight="200">
        <DataGrid.ItemTemplate>
            <DataTemplate>
                <ContentPresenter Content="{Binding Converter={StaticResource ToDisplayConverter}}"/>
            </DataTemplate>
        </DataGrid.ItemTemplate>
    </DataGrid>
</StackPanel>
</Window>

And Finally the converter

using System;
using System.Globalization;
using System.Windows.Data;

namespace TestApp
{
public class DataObjectToDisplayDataObjectConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value != null && value.GetType() == typeof(DataObject))
        {
            DataObject dataObj = (DataObject)value;
            dataObj.GetDisplayObject();
            return dataObj;
        }
        return "Invalid Value";
    }

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

I am open to suggestions, but this is a small example. In our actual application changing one thing will most likely cascade into a huge ordeal.

If I were you I would have change close coupling of view with ViewModel, Like creating new MainWindowViewModel class & having all properties in it.

Another thing I see GetDisplayObject method, what's the need of calling such a method from converter.

You can re-factor this code & put in the converter something like this.

public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
    DataObject dataObj = value as DataObject;

    if (value == null)
    {
        return "Invalid Value";
    }

    if (dataObj.Object1 == null)
    {
        return "Object1";
    }

    return "Object2";
 }

Luckily since it was a matter of if one object was null and it's related strings were empty, I was able to use Priority Binding with a converter to return DependencyProperty.UnsetValue and force it to use the next binding. I think this was the best solution for the situation.

So the xaml ended up looking like this.

    <DataGrid ItemsSource="{Binding DataObjectCollection}" SelectedItem="{Binding SelectedItem}" MinHeight="200">
        <DataGrid.Columns>
            <DataGridTemplateColumn>
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <TextBlock>
                            <TextBlock.Text>
                                <PriorityBinding>
                                    <Binding Path="Object1" Converter="{StaticResource EmptyStringToDependencyPropertyUnset}"/>
                                    <Binding Path="Object2"/>
                                </PriorityBinding>
                            </TextBlock.Text>
                        </TextBlock>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
        </DataGrid.Columns>
    </DataGrid>

And the Converter ended up like this

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if(value == null || value as string == string.Empty)
        {
            return DependencyProperty.UnsetValue;
        }
        return value;
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM