Cannot apply cellValuePresenter/TypeConverter to X

2019-07-20 13:29发布

I also asked this at Infragistics, but don't know how to format my code there, so here it is properly formatted.

My goal is to present a table of structured data with the text of each cell using multiple colors. I have a typeconverter which will convert the data stored in a custom class to a label or textblock containing several text elements with different colors. The data is provided in a datatable (any method that works will be fine) and each value is correctly applied to a cell.

The problem is that instead of using my TypeConverter it uses the ToString method, which I override so I know the model correct model data is mapped on the grid cell by cell. Also the ControlTemplate properties that I'm using are not applied, which tells me the ControlTemplate is not being used.

A concern is it may not be possible to have text where different letters have different colors in a datagrid. If so, is there another way that can be done while still having a good user experience and keeping the design in the xaml file (which is hard with a grid).

As I understand it my code should define a custom CellValuePresenter, can anyone please help me to apply it?

I'm posting my relevant code here. Most of it is obfuscated so please don't focus on spelling errors.

  <Window x:Class="ViewName"
          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
          xmlns:local="clr-namespace:LocalNamespace"
          xmlns:ViewModel="clr-namespace:LocalNamespace.ViewModel"
          xmlns:model="clr-namespace:LocalNamespace.Model"
          xmlns:igDP="http://infragistics.com/DataPresenter"
          >
      <Window.Resources>
          <local:Converter x:Key="converter" />
          <ViewModel:ViewModelLocator x:Key="viewModelLocator" />
          <Style TargetType="{x:Type igDP:CellValuePresenter}" x:Key="cellTemplate" x:Name="cellTemplate" >
              <Setter Property="Template">
                  <Setter.Value>
                      <ControlTemplate TargetType="{x:Type igDP:CellValuePresenter}">
                          <Label 
                              Content="{Binding Converter={StaticResource converter}}"
                              Width="200"
                              MaxWidth="600"
                              MinHeight="20"
                              />
                      </ControlTemplate>
                  </Setter.Value>
              </Setter>
          </Style>
      </Window.Resources>
        <StackPanel Name="stackPanel">
            <igDP:XamDataGrid Name="DifferenceGrid" DataSource="{Binding Source={StaticResource viewModelLocator}, Path=ViewModel.Model}" 
                              ScrollViewer.CanContentScroll="True" ScrollViewer.HorizontalScrollBarVisibility="Visible" ScrollViewer.VerticalScrollBarVisibility="Visible">
                <igDP:XamDataGrid.FieldLayouts>
                    <igDP:FieldLayout>
                        <igDP:FieldLayout.Fields>
                            <igDP:Field>
                                <igDP:Field.Settings>
                                    <igDP:FieldSettings 
                                        CellValuePresenterStyle="{StaticResource cellTemplate}">
                                    </igDP:FieldSettings>
                                </igDP:Field.Settings>
                            </igDP:Field>
                        </igDP:FieldLayout.Fields>
                    </igDP:FieldLayout>
                </igDP:XamDataGrid.FieldLayouts>
            </igDP:XamDataGrid>
        </StackPanel>
  </Window>

    class ViewModelLocator
    {
        private static ViewModel viewModel = new ViewModel();

        public ViewModel ViewModel
        {
            get
            {
                return viewModel;
            }
        }
    }

  public class ViewModel
    {
        private DataTable model;

        public DataTable Model
        {
            get
            {
                return this.model;
            }

            private set
            {
                this.model = value;
            }
        }

    [global::System.ComponentModel.TypeConverter(typeof(Model.CustomClass))]
    public class Converter : TypeConverter, IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (this.CanConvertTo(targetType))
            {
                return this.ConvertTo(value);
            }
            else
            {
                return null;
            }
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (this.CanConvertFrom(targetType))
            {
                return this.ConvertFrom(value);
            }
            else
            {
                return null;
            }
        }

        public new bool CanConvertFrom(Type sourceType)
        {
            // Textboxes don't need to be converted back.
            return sourceType == typeof(Model.CustomClass);
        }

        public new bool CanConvertTo(Type destinationType)
        {
            return destinationType == typeof(Model.CustomClass);
        }

        public object ConvertTo(object value)
        {
            return this.ConvertCustomClassToTextBlock(value);
        }

        public new object ConvertFrom(object value)
        {
            return this.ConvertCustomClassToTextBlock(value);
        }

        private object ConvertCustomClassToTextBlock(object value)
        {
            TextBlock text = new TextBlock();
            Label cell = new Label();

      // Construct the TextBlock.

      cell.Context = text;
      return text; // Or cell, whatever works.
    }
  }

2条回答
在下西门庆
2楼-- · 2019-07-20 13:38

XamDataGrid cells contain editors and for values that should be presented as text, editor is XamTextEditor.

You must modify template of XamTextEditor and provide your own like in the following code:

<Window x:Class="XamDataGridApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:igDP="http://infragistics.com/DataPresenter"
        xmlns:igEditors="http://infragistics.com/Editors"
        xmlns:local="clr-namespace:XamDataGridApp"
        Title="Colorful XamDataGrid" SizeToContent="WidthAndHeight">
    <igDP:XamDataGrid FieldLayoutInitialized="OnFieldLayoutInitialized">
        <igDP:XamDataGrid.DataSource>
            <!-- Replace this with your data source. -->
            <local:DataSourceMock/>
        </igDP:XamDataGrid.DataSource>
        <igDP:XamDataGrid.Resources>
            <local:XamTextEditorConverter x:Key="XamTextEditorConverter" x:Shared="True"/>
            <Style TargetType="igEditors:XamTextEditor" BasedOn="{StaticResource {x:Type igEditors:XamTextEditor}}">
                <Style.Setters>
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="{x:Type igEditors:XamTextEditor}">
                                <Border x:Name="MainBorder" 
                                        Background="{TemplateBinding Background}"
                                        BorderBrush="{TemplateBinding BorderBrush}"
                                        BorderThickness="{TemplateBinding BorderThickness}"
                                        SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}">
                                    <ContentPresenter
                                        Margin="{TemplateBinding Padding}"
                                        HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                        VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
                                        <ContentPresenter.Content>
                                            <MultiBinding Converter="{StaticResource XamTextEditorConverter}">
                                                <Binding Path="DisplayText" RelativeSource="{RelativeSource TemplatedParent}"/>
                                                <Binding RelativeSource="{RelativeSource TemplatedParent}"/>
                                            </MultiBinding>
                                        </ContentPresenter.Content>
                                    </ContentPresenter>
                                </Border>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Style.Setters>
            </Style>
            <Style TargetType="igEditors:XamTextEditor" x:Key="CustomClassXamTextEditorStyle" x:Shared="True" BasedOn="{StaticResource {x:Type igEditors:XamTextEditor}}">
                <Setter Property="ValueToDisplayTextConverter" Value="{x:Static local:CustomClassToStringConverter.Instance}"/>
                <Setter Property="ValueToTextConverter" Value="{x:Static local:CustomClassToStringConverter.Instance}"/>
            </Style>
        </igDP:XamDataGrid.Resources>
    </igDP:XamDataGrid>
</Window>

Following IMultiValueConverter is used to generate XamTextEditor content imperatively:

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Media;
using Infragistics.Windows.DataPresenter;
using Infragistics.Windows.Editors;

namespace XamDataGridApp
{
    class XamTextEditorConverter : IMultiValueConverter
    {
        private static readonly IList<Brush> colors = new Brush[] { Brushes.Red, Brushes.Green, Brushes.Blue };

        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            var displayText = (string)values[0] ?? string.Empty;

            // Context that can be used for custom coloring.
            var editor = (XamTextEditor)values[1];
            var dataItemPresenter = editor.Host as DataItemPresenter;

            // Context that can be used for custom coloring.
            var dataValue = editor.Value;
            var dataItem = dataItemPresenter != null ? dataItemPresenter.Record.DataItem : null;

            var textBlock = new TextBlock()
            {
                TextWrapping = editor.TextWrapping,
                TextAlignment = editor.TextAlignment
            };

            for (int i = 0; i < displayText.Length; ++i)
                textBlock.Inlines.Add(new Run(displayText[i].ToString()) { Foreground = colors[i % colors.Count] });

            return textBlock;
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotSupportedException();
        }
    }
}

ContentPresenter inside XamTextEditor template must be data-bound to editor's DisplayText property because XamDataGrid cells are virtualized and with the binding we instruct XamDataGrid to regenerate ContentPresenter content when cell virtualization kicks in (when CellValuePresenter gets reused to present another value). Also, because of the data-binding, content will be regenerated even if it is changed outside of XamDataGrid.

MultiBinding is used to pass a context (XamTextEditor) from which data value and data item can be extracted and used in a coloring logic. If context is not required, simple binding to DisplayText with IValueConverter can be used instead.


UPDATE:

In order for your converter to be used by XamDataGrid, you must define CellValuePresenterStyle in FieldSettings (not FieldLayouts) like in the following XAML snippet:

<igDP:XamDataGrid Name="DifferenceGrid" DataSource="{Binding Source={StaticResource viewModelLocator}, Path=ViewModel.Model}" 
                      ScrollViewer.CanContentScroll="True" ScrollViewer.HorizontalScrollBarVisibility="Visible" ScrollViewer.VerticalScrollBarVisibility="Visible">
    <igDP:XamDataGrid.FieldSettings>
        <igDP:FieldSettings CellValuePresenterStyle="{StaticResource cellTemplate}"/>
    </igDP:XamDataGrid.FieldSettings>
</igDP:XamDataGrid>

With this code snippet, you specify default CellValuePresenterStyle for all cells in a XamDataGrid.

If you want to specify that your CustomClass column uses XamTextEditor and that all other columns use their own default editor, you can do it by handling FieldLayoutInitialized event like in the following code:

private void OnFieldLayoutInitialized(object sender, FieldLayoutInitializedEventArgs e)
{
    foreach (var field in e.FieldLayout.Fields)
        if (field.DataType == typeof(Model.CustomClass))
        {
            field.Settings.EditorType = typeof(XamTextEditor);
            // Set Editor style in which display converter and edit converter are specified.
            field.Settings.EditorStyle = (Style)((FrameworkElement)sender).FindResource("CustomClassXamTextEditorStyle");
        }
}

MainWindow.xaml from above is updated to reflect these changes.

Here is also code for CustomClassToStringConverter which I used to convert CustomClass to string and back again when displaying and editing CustomClass in XamDataGrid (I added Text property to CustomClass since I didn't know how you extract text from it):

class CustomClassToStringConverter : IValueConverter
{
    public static CustomClassToStringConverter Instance = new CustomClassToStringConverter();

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return ((CustomClass)value).Text;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return new CustomClass() { Text = (string)value };
    }
}
查看更多
\"骚年 ilove
3楼-- · 2019-07-20 13:46

Thanks I got it working now. Had to provide a seperate datasource with textblocks, don't need the multibinding now. But I don't know if the MVVM thing still applies, it's a pretty ugly solution.

    public DataTable TextBlocks
    {
        get
        {
            Converter converter = new DifferenceToTextConverter();
            DataTable table = new DataTable();

            foreach (DataColumn col in this.Model.Columns)
                table.Columns.Add(col.ColumnName, typeof(TextBlock));

            foreach (DataRow row in this.Model.Rows)
            {
                DataRow addedRow = table.NewRow();
                for (int col = 0; col < row.ItemArray.Length; col++)
                {
                    addedRow[col] = converter.ConvertTo(row[col]);
                }

                table.Rows.Add(addedRow);
            }

            return table;
        }
    }
查看更多
登录 后发表回答