I have stackpanel to which I am adding RadioButtons dynamically as children.
Radiobuttons have content which is integer.
I also have Y x Y grid (size determined from code dynamically), to which I'm adding dynamically Buttons and allow user to change Button's content into string which represents integer number.
Here is where I need help:
After checking arbitrary radiobutton from stackpanel, I'd like all buttons from grid that have same number to have their background color changed.
As I am new to WPF I am not sure how to achieve this and your help would be greatly appreciated.
EDIT:
I made a little progress, what I do is basically bind Button.Content with RadioButton.IsChecked and RadioButton.Content for everybutton and every radiobutton, but I have problem that it only works for last radiobutton here is code (rbuts=parent control of radiobuttons, MyGrid=parent control of buttons):
for (int z = 0; z < boardSize * boardSize; z++)
{
Button b1 = MyGrid.Children[z] as Button;
for (int i = 0; i < boardSize; i++)
{
MultiBinding rbtnBinding = new MultiBinding();
rbtnBinding.Converter = new RadioButtonHighlightConverter();
rbtnBinding.Bindings.Add(new Binding("IsChecked") { Source = rbuts.Children[i] });
rbtnBinding.Bindings.Add(new Binding("Content") { Source = rbuts.Children[i] });
rbtnBinding.Bindings.Add(new Binding("Content") { Source = MyGrid.Children[z] });
rbtnBinding.NotifyOnSourceUpdated = true;
b1.SetBinding(Button.BackgroundProperty, rbtnBinding);
}
}
Its as if I cannot set many different multibindings for same button...
You could place your Button Styles in a Resource Dictionary and bind the Style for the Button and use a Converter
ButtonStyles.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="ButtonStyle1" TargetType="Button">
<Setter Property="Background" Value="Green"/>
<Setter Property="FontSize" Value="12"/>
</Style>
<Style x:Key="ButtonStyle2" TargetType="Button">
<Setter Property="Background" Value="Red"/>
<Setter Property="FontSize" Value="14"/>
</Style>
</ResourceDictionary>
Then for the Button that has this requirement you bind Style to the property of interest
<Button ...
Style="{Binding Path=MyDataProperty,
Converter={StaticResource ButtonStyleConverter}}"/>
And in the Converter you load the ButtonStyles Resource Dictionary and return the desired Style based on the value
public class ButtonStyleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Uri resourceLocater = new Uri("/YourNameSpace;component/ButtonStyles.xaml", System.UriKind.Relative);
ResourceDictionary resourceDictionary = (ResourceDictionary)Application.LoadComponent(resourceLocater);
if (value.ToString() == "Some Value")
{
return resourceDictionary["ButtonStyle1"] as Style;
}
return resourceDictionary["ButtonStyle2"] as Style;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
EDIT :
Added details for converter parameter
<RadioButton Content="None"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<RadioButton.IsChecked>
<Binding Path="MyProperty"
Converter="{StaticResource IntToBoolConverter}">
<Binding.ConverterParameter>
<sys:Int32>0</sys:Int32>
</Binding.ConverterParameter>
</Binding>
</RadioButton.IsChecked>
</RadioButton>
Related
I want to make Show and Hide control for my DataGrid on WPF. I just want to add 2 button (with ContextMenu or something) but i don't know how can i do it.
I just want to select a column from my DataGrid and when i click "Hide" button, it will be hide. When i click "Show" button and it will be show again. Can you help me for this? Thank you.
This is my DataGrid Code;
gridview1.CanUserDeleteRows = false;
gridview1.IsReadOnly = true;
SQLiteConnection baglanti = new SQLiteConnection("Data Source=db/veritabani.s3db");
baglanti.Open();
string komut = "SELECT * FROM belgeler";
SQLiteDataAdapter da = new SQLiteDataAdapter(komut, baglanti);
DataTable dt = new DataTable();
da.Fill(dt);
gridview1.ItemsSource = dt.DefaultView;
First of all I'm not really sure how you could right click a column to show it again if it's hidden.
Apart from that you could try something like this for your ContextStripMenu but I'm away from my PC so I can't be sure it works:
private void dataGridView1_MouseClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
ContextMenu contextMenu = new ContextMenu();
contextMenu.MenuItems.Add(new MenuItem("Hide"));
contextMenu.MenuItems.Add(new MenuItem("Show"));
contextMenu.ItemClicked += new ToolStripItemClickedEventHandler(contexMenu_ItemClicked);
contextMenu.Show(dataGridView1, new Point(e.X, e.Y));
}
}
And this for your event handler:
void contexMenu_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
{
int mouseOverColumn = dataGridView1.HitTest(e.X,e.Y).ColumnIndex;
dataGridView1.Columns[mouseOverColumn].Visible = false;
}
You could do something like this
Note: This is far from perfect, but you will get an idea of how you can solve your problem
If you struggle with something let me know, i try to help then
<DataGrid x:Name="dataGrid">
<DataGrid.Resources>
<local:VisibilityToBooleanConverter x:Key="VisibilityToBooleanConverter"></local:VisibilityToBooleanConverter>
<local:BindingProxy x:Key="BindingProxy" Data="{Binding ElementName=dataGrid, Path=Columns}"></local:BindingProxy>
</DataGrid.Resources>
<DataGrid.ColumnHeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu ItemsSource="{Binding Source={StaticResource BindingProxy}, Path=Data}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="IsCheckable" Value="True"/>
<Setter Property="IsChecked" Value="{Binding Visibility,Converter={StaticResource VisibilityToBooleanConverter}}"/>
<Setter Property="Header" Value="{Binding Header}"/>
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</DataGrid.ColumnHeaderStyle>
...
</DataGrid>
public class VisibilityToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is Visibility visibility)
{
return visibility == Visibility.Visible;
}
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool boolValue)
{
return (boolValue) ? Visibility.Visible : Visibility.Hidden;
}
return Visibility.Hidden;
}
}
Im trying to change the default color of a cell to red based on if the value is over a set limit. Currently, what is happening is that I have a grid with a list of oils with a checkbox. When a checkbox for any particular oil is checked, that oil is added to another grid where I can manipulate its amount. For example, if I check 1 oil, its "UsagePercentage" becomes 100. If I check another, the percentages are updated to 50/50. From there, if I click a button to increment its value by 1, it will change to 66.6/33.3 and so on.
What Im trying to implement based on my code is that if the oil's UsagePercentage is more than the UsageLimit, change the foreground text to red, otherwise keep it LightGray. But when I run the program, the UsagePercentage text is black. I dont't understand why binding UsagePercentage works, but ForegroundColor doesn't.
Oil.cs
public float UsagePercentage
{
get { return mUsagePercentage; }
set
{
mUsagePercentage = value;
NotifyPropertyChanged("UsagePercentage");
}
}
OilList.cs
public void UpdateFormulaWeight()
{
float ftw = mOils.Sum(oil => oil.TotalWeight);
mFormulaTotalWeight = ftw;
NotifyPropertyChanged("UsagePercentage");
foreach (Oil oil in mOils)
{
oil.UsagePercentage = (oil.TotalWeight / mFormulaTotalWeight) * 100f;
if (oil.UsagePercentage > oil.UsageLimit)
ForegroundColor = "Red";
else
ForegroundColor = "LightGray";
NotifyPropertyChanged("ForegroundColor");
NotifyPropertyChanged("UsagePercentage");
}
}
private string ForegroundColor
{
get { return mForegroundColor; }
set
{
mForegroundColor = value;
NotifyPropertyChanged("ForegroundColor");
}
}
GrayToRed.cs
public class GrayToRed : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string input = value as string;
switch(input)
{
case "Red":
return Brushes.Red;
case "LightGray":
return Brushes.LightGray;
default:
return DependencyProperty.UnsetValue;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
MainWindow.xaml
<Window.Resources>
<converters:GrayToRed x:Key="GrayToRed" />
</Window.Resources>
...
<!-- USAGE PERCENTAGE -->
<DataGridTextColumn Header="Formula %" Binding="{Binding UsagePercentage, StringFormat={}{0:F2}%}" IsReadOnly="True" Width="64">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="Background" Value="#1e1e1e"/>
<Setter Property="Foreground" Value="{Binding ForegroundColor, Converter={StaticResource GrayToRed}}"/>
</Style>
</DataGridTextColumn.CellStyle>
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="TextBlock.VerticalAlignment" Value="Center" />
<Setter Property="TextBlock.TextAlignment" Value="Center" />
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
DataGrid.ItemSource gets set in code behind to a dataview. Column names and count needs to change on the fly, so a list of objects doesn't work very well.
I have the table display working completely correctly. Which is great, but my GridCellStyle passes the DataRowView to the converter, instead of the DataGridCell which is what I expected to pass.
Is there a way to either get the DataGridCell content OR something indicating which column is current passed to my converter?
<UserControl x:Class="TestUI.SilveusTable"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:TestUI"
mc:Ignorable="d"
>
<UserControl.Resources>
<local:CbotValueConverter x:Key="CbotValueConverter" />
<Style x:Key="GridCellStyle" TargetType="{x:Type DataGridCell}">
<Setter Property="Background" Value="Yellow"/>
<Setter Property="Foreground" Value="{Binding Converter={StaticResource CbotValueConverter}}"/>
</Style>
</UserControl.Resources>
<Grid>
<DataGrid x:Name="DataGrid1" IsReadOnly="True" CellStyle="{StaticResource GridCellStyle}" />
</Grid>
</UserControl>
Here is my Converter Class
[ValueConversion(typeof(DataGridCell), typeof(SolidColorBrush))]
public class CbotValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var dgc = (DataGridCell) value;
var rowView = (DataRowView) dgc.DataContext;
var v = rowView.Row.ItemArray[dgc.Column.DisplayIndex];
decimal amount;
if (decimal.TryParse(v.ToString(), out amount))
{
if (amount >= 10) return Brushes.Red;
if (amount >= 5) return Brushes.Blue;
if (amount > 0) return Brushes.Green;
}
return Brushes.Black; //quantity should not be below 0
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
change the Source of a binding
with currect binding declaration, DataContext of DataGridCell is the Source
Value="{Binding Converter={StaticResource CbotValueConverter}}"
to use DataGridCell itself add RelativeSource Self part (refers to the element on which you are setting the binding)
Value="{Binding Converter={StaticResource CbotValueConverter},
RelativeSource={RelativeSource Self}}"
So far I've successfully created a data series using WPF Toolkit Data Visualization Controls, like this:
As you can see, there is 3 data series (top, middle, bottom). I'm using AreaSeries for each and apply this style:
<Style x:Key="TopAreaSeriesStyle" TargetType="{x:Type chart:AreaSeries}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type chart:AreaSeries}">
<Canvas x:Name="PlotArea">
<Path StrokeThickness="1.5" Stroke="Black" Fill="Yellow" Opacity=".4">
<Path.Style>
<Style TargetType="{x:Type Path}">
<Setter Property="Data">
<Setter.Value>
<MultiBinding Converter="{StaticResource geoExclusionConverter}">
<Binding ElementName="TopAreaSeries" Path="Geometry"/>
<Binding ElementName="MiddleAreaSeries" Path="Geometry"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</Path.Style>
</Path>
</Canvas>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="MiddleAreaSeriesStyle" TargetType="{x:Type chart:AreaSeries}"...>
<Style x:Key="BottomAreaSeriesStyle" TargetType="{x:Type chart:AreaSeries}"...>
I have to create this style so that colored area is rendered only between area series. For each area series, I must apply this style, so there is 3 styles that differ in where area of clipping occurs. For example, Area of the top AreaSeries that collided with area of the middle AreaSeries is clipped and so does the middle AreaSeries to the bottom AreaSeries.
The clipping process is handled by a converter class which consumed 2 parameters defined in MultiBinding tag. First parameter takes the geometry that will be clipped, and second parameter take the geometry that is used to clip.
public class GeometryExclusionConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (values[0] == null && values[1] == null)
return new PathGeometry();
else
{
var geo1 = values[0] as Geometry;
var geo2 = values[1] as Geometry;
return new CombinedGeometry(GeometryCombineMode.Exclude, geo1, geo2);
}
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
By using this method, I can get all I need. But the problem is, there can be more than 7 data series, so that I must create a lot of styles that differs only in parameters that get passed. And it makes a lot of dependency (style and converter). It would be nice if all this stuff can be handled in one place.
I've searched on how to create a style with parameters so I can somehow apply a style by passing parameters on it, but none of them seemed working.
UPDATE
Using advice proposed by #AnjumSKhan, I am able to simplify the styling, by putting those code on OnInitialized event.
public class ClippedAreaSeries : AreaSeries
{
public string ClippedArea
{
get { return (string)GetValue(ClippedAreaProperty); }
set { SetValue(ClippedAreaProperty, value); }
}
public static readonly DependencyProperty ClippedAreaProperty =
DependencyProperty.Register("ClippedArea", typeof(string), typeof(ClippedAreaSeries), new PropertyMetadata(null));
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
string templateXml =
#"
<ControlTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
>
<Canvas x:Name='PlotArea'>
<Path StrokeThickness='1.5' Stroke='Black' Fill='Yellow' Opacity='.25'>
<Path.Style>
<Style TargetType='{x:Type Path}'>
<Setter Property='Data'>
<Setter.Value>
<MultiBinding Converter='{StaticResource geoExclusionConverter}'>
<Binding ElementName='{0}' Path='Geometry'/>
<Binding ElementName='{1}' Path='Geometry'/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</Path.Style>
</Path>
</Canvas>
</ControlTemplate>
";
templateXml = templateXml.Replace("{0}", this.Name).Replace("{1}", this.ClippedArea);
//so on...
}
}
Notice that, it still depends on a converter class that must be defined in Application.Resources. It would be nice if this control doesn't depend on them, so there is no need to define the converter as resources. Any ideas are appreciated.
You can easily set the ControlTemplate using code by using System.Windows.Markup.XamlReader.Load(System.IO.Stream stream) method.
Below code shows how to change the Control template of Button :
string templateXml =
#"<ControlTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Canvas Background='Purple'>
<TextBlock Text='custom btn'/>
</Canvas>
</ControlTemplate>";
ControlTemplate template;
using (var stringReader = new System.IO.StringReader(templateXml))
using (XmlReader xmlReader = XmlReader.Create(stringReader))
{
template = (ControlTemplate)XamlReader.Load(xmlReader);
}
Btn1.Template = template;
You can use this approach for your scenario, and replace necessary values in the string.
But now if we try to apply converters, the above approach creates problems. So, we now go for pure-code approach, and construct all elements ourselves. This approach involves using FrameworkElementFactory class. For example, if we want to change ControlTemplate of Button and apply a converter to Text property of TextBlock like below :
<ControlTemplate TargetType="Button">
<Canvas Background='Red'>
<TextBlock>
<TextBlock.Text>
<Binding Path='Content' RelativeSource="{RelativeSource Mode=TemplatedParent}" >
<Binding.Converter>
<converter:ContentConverter/>
</Binding.Converter>
</Binding>
</TextBlock.Text>
</TextBlock>
</Canvas>
</ControlTemplate>
We will write the corresponding code as follows :
FrameworkElementFactory canvas = new FrameworkElementFactory(typeof(Canvas));
canvas.SetValue(Canvas.BackgroundProperty, new SolidColorBrush(Colors.Red));
FrameworkElementFactory tb = new FrameworkElementFactory(typeof(TextBlock));
Binding binding = new Binding("Content");
binding.RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent);
binding.Converter = new Converters.ContentConverter();
tb.SetBinding(TextBlock.TextProperty, binding);
canvas.AppendChild(tb);
ControlTemplate ct = new ControlTemplate(typeof(Button));
ct.VisualTree = canvas;
Btn1.Template = ct;
Based on above approach, the control-template style which you have posted would be written as :
FrameworkElementFactory canvas = new FrameworkElementFactory(typeof(Canvas));
canvas.Name = "PlotArea";
FrameworkElementFactory path = new FrameworkElementFactory(typeof(Path));
{
path.SetValue(Path.StrokeThicknessProperty, 1.5);
path.SetValue(Path.StrokeProperty, new SolidColorBrush(Colors.Green));
path.SetValue(Path.OpacityProperty, 0.25);
MultiBinding binding = new MultiBinding();
// create your converter properly below
binding.Converter = new Converters.GeoConverter();
Binding bindingItem1 = new Binding();
bindingItem1.ElementName = "AreaPlus1SD";
bindingItem1.Path = new PropertyPath("Geometry");
Binding bindingItem2 = new Binding();
bindingItem2.ElementName = "AreaMedian";
bindingItem2.Path = new PropertyPath("Geometry");
binding.Bindings.Add(bindingItem1);
binding.Bindings.Add(bindingItem2);
path.SetBinding(Path.DataProperty, binding);
}
canvas.AppendChild(path);
ControlTemplate ct = new ControlTemplate(typeof(ChartingToolkit.AreaSeries));
ct.VisualTree = canvas;
AreaSeries1.Template = ct;
You can do it like this:
var customStyle = new Style(typeof (Button));
customStyle.Setters.Add(new Setter{Property = Button.BackgroundProperty, Value = new SolidColorBrush(){Color = Colors.Red}});
ButtonTest.Style = customStyle;
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.
}
}
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 };
}
}
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;
}
}