When I try to bind a ResourceDictionary item against Rectangle.Child, I get an exception:
ArgumentException: Value does not fall within the expected range.
Here is an example:
<UserControl.Resources>
<local:PersonConverter x:Key="MyConverter"/>
</UserControl.Resources>
<ListBox ItemsSource="{Binding Persons}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border Child="{Binding Gender, Converter={StaticResource MyConverter}}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And the code behind:
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
Persons = new List<Person> {new Person("Female"), new Person("Male")};
}
public List<Person> Persons { get; private set; }
}
public class PersonConverter : IValueConverter
{
private ResourceDictionary Items { get; set; }
public PersonConverterRes()
{
Items = new ResourceDictionary
{
{"Male", new Canvas() {
Background = new SolidColorBrush(Colors.Blue),
Height = 100, Width = 100}},
{"Female", new Canvas() {
Background = new SolidColorBrush(Colors.Magenta),
Height = 100, Width = 100}}
};
}
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
return Items[value.ToString()];
}
...
}
public class Person
{
public Person(String gender)
{
Gender = gender;
}
public String Gender { get; private set; }
}
But if I replace the ResourceDictionary with a plain Dictionary<String, UIElement> the binding works fine:
public class PersonConverter : IValueConverter
{
private Dictionary<String, UIElement> Items { get; set; }
public PersonConverterRes()
{
Items = new Dictionary<String, UIElement>
{
{"Male", new Canvas() {
Background = new SolidColorBrush(Colors.Blue),
Height = 100, Width = 100}},
{"Female", new Canvas() {
Background = new SolidColorBrush(Colors.Magenta),
Height = 100, Width = 100}}
};
}
...
}
Does anybody know what is causing this exception?
Note:
I have tried this under WinRT as well. There, the code doesn't throw an exception, but the binding still doesn't work if I use a ResourceDictionary. I guess it's probably failing silently.
You can not use databinding to bind to the Child property of a Border since it is not a DependencyProperty. This is why your ResourceDictionary approach does not work.
Also, databinding in WPF/Silvelight/WinRT fails silently by design (it's a feature, and a very useful one if used correctly), so your guess would be right on that.
Don't do this.
More elegant to set a the Canvas.Background with trigger
<ListBox ItemsSource="{Binding Persons}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border <!-- set properties --> >
<Canvas Height="100" Width="100">
<Canvas.Style>
<Style TargetType="Canvas">
<Style.Triggers>
<DataTrigger Binding={Binding Gender} Value="Male">
<Setter Property="Background" Value="Blue"/>
</DataTrigger>
<DataTrigger Binding={Binding Gender} Value="Female">
<Setter Property="Background" Value="Magenta"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Canvas.Style>
</Canvas>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Related
OK so this is definitely a newbie question that unfortunately could not figure/find the answer to.
Essentially binding a list of objects to a Combobox, when the Disabled property on the object is set to true I want the text colour of the Combobox item to be set to gray.
This is what I have so far:
Combobox item datatype
public class ListItem
{
public ListItem(string text)
{
Text = text;
}
public string Text { get; set; }
public bool Disabled { get; set; }
}
Viewmodel setup
public class MainPageViewModel : ReactiveObject
{
// In ReactiveUI, this is the syntax to declare a read-write property
// that will notify Observers, as well as WPF, that a property has
// changed. If we declared this as a normal property, we couldn't tell
// when it has changed!
private ListItem _selectedItem;
public ListItem SelectedItem
{
get => _selectedItem;
set => this.RaiseAndSetIfChanged(ref _selectedItem, value);
}
public List<ListItem> Items { get; set; }
public MainPageViewModel()
{
Items = new List<ListItem>
{
new ListItem ("A Cat"),
new ListItem ("A Dog"),
new ListItem ("A Mouse"),
new ListItem ("A Frog") { Disabled = true }
};
}
}
ReactiveUI Binding
public MainPage()
{
InitializeComponent();
ViewModel = new MainPageViewModel();
this.WhenActivated(d =>
{
this.OneWayBind(ViewModel, vm => vm.Items, v => v.MyComboBox.ItemsSource)
.DisposeWith(d);
this.Bind(ViewModel, vm => vm.SelectedItem, v => v.MyComboBox.SelectedItem)
.DisposeWith(d);
});
}
Xaml markup
<ComboBox
Name="MyComboBox"
Margin="0,0,0,20"
Foreground="black">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Disabled}" Value="True">
<Setter Property="Foreground" Value="Gray" />
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
Any help is appreciated let me know if you need more information.
Solution: It looks like in future I need to test the example code before puttin it up - our actual code had the Disabled property set as a readonly which must mess with WPF binding. Changing it to public set and get solved the first issue of not seeing it greyed out! It would seem staring at a problem for so long blinds you and it really is that simple.
As for graying out the selected item I will try it out and see.
The last item in the dropdown already has its text grayed out, so I assume you're asking about the selected item. The ComboBox uses separate data templates for the selected item and the items in the dropdown. You can use a DataTemplateSelector to set both.
public class ComboBoxTemplateSelector : DataTemplateSelector
{
public DataTemplate SelectedItemTemplate { get; set; }
public DataTemplate DropdownItemsTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var itemToCheck = container;
// Search up the visual tree, stopping at either a ComboBox or a ComboBoxItem (or null).
// This will determine which template to use.
while (itemToCheck is not null and not ComboBox and not ComboBoxItem)
itemToCheck = VisualTreeHelper.GetParent(itemToCheck);
// If you stopped at a ComboBoxItem, you're in the dropdown.
return itemToCheck is ComboBoxItem ? DropdownItemsTemplate : SelectedItemTemplate;
}
}
Xaml markup
<StackPanel>
<StackPanel.Resources>
<Style x:Key="GrayedOutText" TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding Disabled}" Value="True">
<Setter Property="Foreground" Value="Gray" />
</DataTrigger>
</Style.Triggers>
</Style>
<local:ComboBoxTemplateSelector x:Key="ComboBoxTemplateSelector">
<local:ComboBoxTemplateSelector.SelectedItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}" Style="{StaticResource GrayedOutText}" />
</DataTemplate>
</local:ComboBoxTemplateSelector.SelectedItemTemplate>
<local:ComboBoxTemplateSelector.DropdownItemsTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}" Style="{StaticResource GrayedOutText}" />
</DataTemplate>
</local:ComboBoxTemplateSelector.DropdownItemsTemplate>
</local:ComboBoxTemplateSelector>
</StackPanel.Resources>
<ComboBox
Name="MyComboBox"
Margin="0,0,0,20"
ItemTemplateSelector="{StaticResource ComboBoxTemplateSelector}">
</ComboBox>
</StackPanel>
We have some repetition in the DataTemplate definitions, but these tend to grow apart in production code.
Resources
Can I use a different Template for the selected item in a WPF ComboBox than for the items in the dropdown part?
https://www.reactiveui.net/docs/getting-started/compelling-example
I'm assuming your problem is that ComboBoxItems do not get grayed once the app is running.
I'm not familiar with ReactiveUI, but since I found a problem in your code, I tried it in a CommunityToolkit.Mvvm version of your code and verified my theory.
Bottom of line, you need to implement the ReactiveUI version of INotifyPropertyChanged to the Disabled property.
If you are interested in, I can post the CommunityToolkit.Mvvm version of this code.
Here is an approach that worked in my tests:
Combobox item datatype:
//-- Unchanged
public class ListItem
{
public ListItem( string text )
{
Text = text;
}
public string Text { get; set; }
public bool Disabled { get; set; }
}
Viewmodel setup:
public class MainPageViewModel : INotifyPropertyChanged
{
private ListItem? _selectedItem;
public event PropertyChangedEventHandler? PropertyChanged;
public ListItem? SelectedItem
{
get => _selectedItem;
set
{
//-- I didn't had the "RaiseAndSetIfChanged" method, so I just implemented the functionality manually
if( value != _selectedItem )
{
//-- Update the value ...
_selectedItem = value;
//-- ... AND inform everyone (who is interested) about the change
this.PropertyChanged?.Invoke( this, new PropertyChangedEventArgs( nameof( this.SelectedItem ) ) );
}
}
}
//-- Use always an ObservableCollection when you want to achieve reactivity
public ObservableCollection<ListItem> Items
{ get; } = new ObservableCollection<ListItem>();
public MainPageViewModel()
{
//-- Add some test data
this.Items.Add( new ListItem( "A Cat" ) );
this.Items.Add( new ListItem( "A Dog" ) );
this.Items.Add( new ListItem( "A Mouse" ) );
this.Items.Add( new ListItem( "A Frog" ) { Disabled = true } );
//-- Just select the first item
this.SelectedItem = this.Items[0];
}
}
Main page:
public MainPage()
{
//-- Define the DataContext BEFORE the UI will be initialized ;)
this.DataContext = new MainPageViewModel();
InitializeComponent();
//-- Never saw such code before -> just don't do that ;)
//this.WhenActivated( d =>
//{
// this.OneWayBind( ViewModel, vm => vm.Items, v => v.MyComboBox.ItemsSource )
// .DisposeWith( d );
// this.Bind( ViewModel, vm => vm.SelectedItem, v => v.MyComboBox.SelectedItem )
// .DisposeWith( d );
//} );
}
Xaml markup:
<DockPanel>
<ComboBox
DockPanel.Dock="Top"
Name="MyComboBox"
Margin="0,0,0,20"
Foreground="black"
ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}" />
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Disabled}" Value="True">
<Setter Property="Foreground" Value="Gray" />
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
<!-- Details View -->
<StackPanel>
<!-- name -->
<StackPanel Orientation="Horizontal">
<Label Content="Item Name" />
<TextBox Text="{Binding SelectedItem.Text}" />
</StackPanel>
<!-- disabled flag -->
<StackPanel Orientation="Horizontal">
<Label Content="IsDisabled" />
<CheckBox IsChecked="{Binding SelectedItem.Disabled}" />
</StackPanel>
</StackPanel>
</DockPanel>
I hope this will satisfy your requirements. Have fun :)
I'm building an application with C# WPF. And I'm using MVVM architecture. I want to display some letters on an image in the given coordinates. So far, the only thing I was able to do is to render some shapes like rectangles using 'Geometry' class.
I have attached an image for references.
The grid is an image(a PNG file)
And below is my current code for the view model and XAML file.
ViewModel
public class InspectionGridViewModel : RegionViewModelBase
{
public ObservableCollection<Drawing> Drawings { get; set; }
public InspectionGridViewModel(IRegionManager regionManager, ILogger<InspectionGridViewModel> logger, MainModuleConfiguration configuration) : base(regionManager, logger)
{
PlotMarkersInTheGrid();
}
public void PlotMarkersInTheGrid()
{
Drawings = new ObservableCollection<Drawing>();
Drawings.Add(new Drawing
{
Geometry = new StreamGeometry(),
Fill = Brushes.LightBlue,
Stroke = Brushes.Blue,
StrokeThickness = 2
});
Drawings.Add(new Drawing
{
Geometry = new RectangleGeometry(new Rect(50, 150, 100, 60)),
Fill = Brushes.LightGreen,
Stroke = Brushes.Green,
StrokeThickness = 2
});
}
}
public class Drawing
{
public Geometry Geometry { get; set; }
public Brush Fill { get; set; }
public Brush Stroke { get; set; }
public double StrokeThickness { get; set; }
}
XAML file
<ItemsControl ItemsSource="{Binding Drawings}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Path Data="{Binding Geometry}"
Fill="{Binding Fill}"
Stroke="{Binding Stroke}"
StrokeThickness="{Binding StrokeThickness}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
First thing I wanna know. Is this possible to do? If so, can anyone help me with this?
How about rather than messing with geometry, you use TextBlock and use a ViewBox to scale it? You could just place these in a Canvas similiar to what you're attempting to do with those geometry items. So, your XAML would be very similiar to what you already have, but template the items into a ViewBox.
<ItemsControl ItemsSource="{Binding Path=Markers}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Viewbox Width="{Binding Size}" Height="{Binding Size}">
<TextBlock Text="{Binding MarkerText}" />
</Viewbox>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Path=PosX}" />
<Setter Property="Canvas.Top" Value="{Binding Path=PosY}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
Note that a ItemContainerStyle is used for positioning since the direct child of the canvas is required to use the Canvas.Left and Canvas.Top properties.
With that in place and view models like these ..
public class InspectionGridViewModel
{
public ObservableCollection<MarkerViewModel> Markers { get; } = new ObservableCollection<MarkerViewModel>();
public InspectionGridViewModel()
{
Markers = new ObservableCollection<MarkerViewModel>()
{
new MarkerViewModel()
{
MarkerText = "A",
Size = 50,
PosX = 10,
PosY = 20
},
new MarkerViewModel()
{
MarkerText = "B",
Size = 100,
PosX = 80,
PosY = 50
}
};
}
}
public class MarkerViewModel
{
public string MarkerText { get; set; }
public int PosX { get; set; }
public int PosY { get; set; }
public int Size { get; set; }
}
I get the following result. I think this is what you're looking for.
I have got a WPF datagrid and I want diffrent cell colours according to values. I have got below code on my xaml
Style TargetType="DataGridCell"
but instead of selecting a cell only is selecting all row? What am I missing?
If you try to set the DataGrid.CellStyle the DataContext will be the row, so if you want to change the colour based on one cell it might be easiest to do so in specific columns, especially since columns can have varying contents, like TextBlocks, ComboBoxes and CheckBoxes. Here is an example of setting all the cells light-green where the Name is John:
<DataGridTextColumn Binding="{Binding Name}">
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<Trigger Property="Text" Value="John">
<Setter Property="Background" Value="LightGreen"/>
</Trigger>
</Style.Triggers>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
You could also use a ValueConverter to change the colour.
public class NameToBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string input = (string)value;
switch (input)
{
case "John":
return Brushes.LightGreen;
default:
return DependencyProperty.UnsetValue;
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
Usage:
<Window.Resources>
<local:NameToBrushConverter x:Key="NameToBrushConverter"/>
</Window.Resources>
...
<DataGridTextColumn Binding="{Binding Name}">
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Background" Value="{Binding Name, Converter={StaticResource NameToBrushConverter}}"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
Yet another option is to directly bind the Background to a property which returns the respectively coloured brush. You will have to fire property change notifications in the setters of properties on which the colour is dependent.
e.g.
public string Name
{
get { return _name; }
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged(nameof(Name));
OnPropertyChanged(nameof(NameBrush));
}
}
}
public Brush NameBrush
{
get
{
switch (Name)
{
case "John":
return Brushes.LightGreen;
default:
break;
}
return Brushes.Transparent;
}
}
If you need to do it with a set number of columns, H.B.'s way is best. But if you don't know how many columns you are dealing with until runtime, then the below code [read: hack] will work. I am not sure if there is a better solution with an unknown number of columns. It took me two days working at it off and on to get it, so I'm sticking with it regardless.
C#
public class ValueToBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
int input;
try
{
DataGridCell dgc = (DataGridCell)value;
System.Data.DataRowView rowView = (System.Data.DataRowView)dgc.DataContext;
input = (int)rowView.Row.ItemArray[dgc.Column.DisplayIndex];
}
catch (InvalidCastException e)
{
return DependencyProperty.UnsetValue;
}
switch (input)
{
case 1: return Brushes.Red;
case 2: return Brushes.White;
case 3: return Brushes.Blue;
default: return DependencyProperty.UnsetValue;
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
XAML
<UserControl.Resources>
<conv:ValueToBrushConverter x:Key="ValueToBrushConverter"/>
<Style x:Key="CellStyle" TargetType="DataGridCell">
<Setter Property="Background" Value="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource ValueToBrushConverter}}" />
</Style>
</UserControl.Resources>
<DataGrid x:Name="dataGrid" CellStyle="{StaticResource CellStyle}">
</DataGrid>
This may be of help to you. It isn't the stock WPF datagrid however.
I used DevExpress with a custom ColorFormatter behaviour. I couldn't find anything on the market that did this out of the box. This took me a few days to develop. My code attaached below, hopefully this helps someone out there.
Edit: I used POCO view models and MVVM however you could change this to not use POCO if you desire.
Viewmodel.cs
namespace ViewModel
{
[POCOViewModel]
public class Table2DViewModel
{
public ITable2DView Table2DView { get; set; }
public DataTable ItemsTable { get; set; }
public Table2DViewModel()
{
}
public Table2DViewModel(MainViewModel mainViewModel, ITable2DView table2DView) : base(mainViewModel)
{
Table2DView = table2DView;
CreateTable();
}
private void CreateTable()
{
var dt = new DataTable();
var xAxisStrings = new string[]{"X1","X2","X3"};
var yAxisStrings = new string[]{"Y1","Y2","Y3"};
//TODO determine your min, max number for your colours
var minValue = 0;
var maxValue = 100;
Table2DView.SetColorFormatter(minValue,maxValue, null);
//Add the columns
dt.Columns.Add(" ", typeof(string));
foreach (var x in xAxisStrings) dt.Columns.Add(x, typeof(double));
//Add all the values
double z = 0;
for (var y = 0; y < yAxisStrings.Length; y++)
{
var dr = dt.NewRow();
dr[" "] = yAxisStrings[y];
for (var x = 0; x < xAxisStrings.Length; x++)
{
//TODO put your actual values here!
dr[xAxisStrings[x]] = z++; //Add a random values
}
dt.Rows.Add(dr);
}
ItemsTable = dt;
}
public static Table2DViewModel Create(MainViewModel mainViewModel, ITable2DView table2DView)
{
var factory = ViewModelSource.Factory((MainViewModel mainVm, ITable2DView view) => new Table2DViewModel(mainVm, view));
return factory(mainViewModel, table2DView);
}
}
}
IView.cs
namespace Interfaces
{
public interface ITable2DView
{
void SetColorFormatter(float minValue, float maxValue, ColorScaleFormat colorScaleFormat);
}
}
View.xaml.cs
namespace View
{
public partial class Table2DView : ITable2DView
{
public Table2DView()
{
InitializeComponent();
}
static ColorScaleFormat defaultColorScaleFormat = new ColorScaleFormat
{
ColorMin = (Color)ColorConverter.ConvertFromString("#FFF8696B"),
ColorMiddle = (Color)ColorConverter.ConvertFromString("#FFFFEB84"),
ColorMax = (Color)ColorConverter.ConvertFromString("#FF63BE7B")
};
public void SetColorFormatter(float minValue, float maxValue, ColorScaleFormat colorScaleFormat = null)
{
if (colorScaleFormat == null) colorScaleFormat = defaultColorScaleFormat;
ConditionBehavior.MinValue = minValue;
ConditionBehavior.MaxValue = maxValue;
ConditionBehavior.ColorScaleFormat = colorScaleFormat;
}
}
}
DynamicConditionBehavior.cs
namespace Behaviors
{
public class DynamicConditionBehavior : Behavior<GridControl>
{
GridControl Grid => AssociatedObject;
protected override void OnAttached()
{
base.OnAttached();
Grid.ItemsSourceChanged += OnItemsSourceChanged;
}
protected override void OnDetaching()
{
Grid.ItemsSourceChanged -= OnItemsSourceChanged;
base.OnDetaching();
}
public ColorScaleFormat ColorScaleFormat { get; set;}
public float MinValue { get; set; }
public float MaxValue { get; set; }
private void OnItemsSourceChanged(object sender, EventArgs e)
{
var view = Grid.View as TableView;
if (view == null) return;
view.FormatConditions.Clear();
foreach (var col in Grid.Columns)
{
view.FormatConditions.Add(new ColorScaleFormatCondition
{
MinValue = MinValue,
MaxValue = MaxValue,
FieldName = col.FieldName,
Format = ColorScaleFormat,
});
}
}
}
}
View.xaml
<UserControl x:Class="View"
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:dxmvvm="http://schemas.devexpress.com/winfx/2008/xaml/mvvm"
xmlns:ViewModels="clr-namespace:ViewModel"
xmlns:dxg="http://schemas.devexpress.com/winfx/2008/xaml/grid"
xmlns:behaviors="clr-namespace:Behaviors"
xmlns:dxdo="http://schemas.devexpress.com/winfx/2008/xaml/docking"
DataContext="{dxmvvm:ViewModelSource Type={x:Type ViewModels:ViewModel}}"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="800">
<UserControl.Resources>
<Style TargetType="{x:Type dxg:GridColumn}">
<Setter Property="Width" Value="50"/>
<Setter Property="HorizontalHeaderContentAlignment" Value="Center"/>
</Style>
<Style TargetType="{x:Type dxg:HeaderItemsControl}">
<Setter Property="FontWeight" Value="DemiBold"/>
</Style>
</UserControl.Resources>
<!--<dxmvvm:Interaction.Behaviors>
<dxmvvm:EventToCommand EventName="" Command="{Binding OnLoadedCommand}"/>
</dxmvvm:Interaction.Behaviors>-->
<dxg:GridControl ItemsSource="{Binding ItemsTable}"
AutoGenerateColumns="AddNew"
EnableSmartColumnsGeneration="True">
<dxmvvm:Interaction.Behaviors >
<behaviors:DynamicConditionBehavior x:Name="ConditionBehavior" />
</dxmvvm:Interaction.Behaviors>
<dxg:GridControl.View>
<dxg:TableView ShowGroupPanel="False"
AllowPerPixelScrolling="True"/>
</dxg:GridControl.View>
</dxg:GridControl>
</UserControl>
In my case convertor must return string value. I don't why, but it works.
*.xaml (common style file, which is included in another xaml files)
<Style TargetType="DataGridCell">
<Setter Property="Background" Value="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource ValueToBrushConverter}}" />
</Style>
*.cs
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Color color = VSColorTheme.GetThemedColor(EnvironmentColors.ToolWindowBackgroundColorKey);
return "#" + color.Name;
}
Just put instead
<Style TargetType="{x:DataGridCell}" >
But beware that this will target ALL your cells (you're aiming at all the objects of type DataGridCell )
If you want to put a style according to the cell type, I'd recommend you to use a DataTemplateSelector
A good example can be found in Christian Mosers' DataGrid tutorial:
http://www.wpftutorial.net/DataGrid.html#rowDetails
Have fun :)
// Example: Adding a converter to a column (C#)
Style styleReading = new Style(typeof(TextBlock));
Setter s = new Setter();
s.Property = TextBlock.ForegroundProperty;
Binding b = new Binding();
b.RelativeSource = RelativeSource.Self;
b.Path = new PropertyPath(TextBlock.TextProperty);
b.Converter = new ReadingForegroundSetter();
s.Value = b;
styleReading.Setters.Add(s);
col.ElementStyle = styleReading;
Based on the answer by 'Cassio Borghi'. With this method, there is no need to change the XAML at all.
DataGridTextColumn colNameStatus2 = new DataGridTextColumn();
colNameStatus2.Header = "Status";
colNameStatus2.MinWidth = 100;
colNameStatus2.Binding = new Binding("Status");
grdComputer_Servives.Columns.Add(colNameStatus2);
Style style = new Style(typeof(TextBlock));
Trigger running = new Trigger() { Property = TextBlock.TextProperty, Value = "Running" };
Trigger stopped = new Trigger() { Property = TextBlock.TextProperty, Value = "Stopped" };
stopped.Setters.Add(new Setter() { Property = TextBlock.BackgroundProperty, Value = Brushes.Blue });
running.Setters.Add(new Setter() { Property = TextBlock.BackgroundProperty, Value = Brushes.Green });
style.Triggers.Add(running);
style.Triggers.Add(stopped);
colNameStatus2.ElementStyle = style;
foreach (var Service in computerResult)
{
var RowName = Service;
grdComputer_Servives.Items.Add(RowName);
}
<DataGridTextColumn Binding="{Binding Name}" Header="Name" Foreground="White">
<DataGridTextColumn.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="Background" Value="{Binding color}" />
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
In xaml.cs file
SolidColorBrush color=new SolidColorBrush(Colors.Red);
To do this in the Code Behind (VB.NET)
Dim txtCol As New DataGridTextColumn
Dim style As New Style(GetType(TextBlock))
Dim tri As New Trigger With {.Property = TextBlock.TextProperty, .Value = "John"}
tri.Setters.Add(New Setter With {.Property = TextBlock.BackgroundProperty, .Value = Brushes.Green})
style.Triggers.Add(tri)
xtCol.ElementStyle = style
I got a problem binding an ItemTemplates Parent context inside an item template.
There are plenty of "workarounds" which only work in WPF (i.e. using FindAncestor and AncestorType). This are out of question, as it's not supported in Windows Store Apps.
Other solutions suggest using ElementName. While this works in Windows Store Apps, it's not an acceptable solution, as it reusable DataTemplates impossible.
One solution I did read about was using Attached Properties/Attached Behaviors, which sounds like the way to go and is a very generic and reusable method. But I couldn't make it work so far.
My current attempt is, to create a global Attached Property and access it in the ItemTemplate.
public class GlobalProperties : DependencyObject
{
public static object GetParentDataContext(DependencyObject obj)
{
return (object)obj.GetValue(ParentDataContextProperty);
}
public static void SetParentDataContext(DependencyObject obj, object value)
{
obj.SetValue(ParentDataContextProperty, value);
}
// Using a DependencyProperty as the backing store for ParentDataContext. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ParentDataContextProperty =
DependencyProperty.RegisterAttached("ParentDataContext",
typeof(object),
typeof(GlobalProperties),
new PropertyMetadata(null, ParentDataContextChanged));
private static void ParentDataContextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
SetParentDataContext(d, e.NewValue);
}
}
And my XAML (simplified by inlining the DataTemplate within the ListViews XAML Code. It will be later stored outside in an DataTemplate.xaml file.
<ListView
my:GlobalProperties.ParentDataContext="{Binding}"
ItemsSource="{Binding Questions}"
>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
<Setter Property="Margin" Value="0,-1,0,0" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid Background="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(my:GlobalProperties.ParentDataContext).Site.Styling.TagBackgroundColor}">
<!-- Item Related DataBindings -->
</Grid>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Where my is the namespace where my GlobalProperties is defined.
When I have a breakpoint in ParentDataContextChanged it's definitely called with the DataContext. But I can't get the XAML code to work to read it. The Background Xaml Property is always empty and the background always remains white (color saved in the TagBackgroundColor property is red).
Anyone know what's wrong with the code/attempt?
update
Also Background="{Binding RelativeSource={RelativeSource Self}, Path=(my:GlobalProperties.ParentDataContext).Site.Styling.TagBackgroundColor}" doesn't work, since that's what there in most website that show attached properties for such a case.
update 2
Example of of the ViewModel for better clarification
public class QuestionsViewModel : ViewModel
{
// Site.Styling.BackgroundColor to be bound to certain ItemTemplate
// Properties, but don't have access to it from the ItemTemplate
// because it has the context of one item from Questions
public Site Site { get; set; };
// Questions bound to ListView.DataSource > becomes Item's DataContext
public IEnumerable<Question> Questions { get; set; };
}
public class Site
{
public Style Styling { get; set; }
}
public class Style
{
public string ForegroundColor { get; set; }
public string BackgroundColor { get; set; }
public string LinkColor { get; set; }
}
With reference to above comment, I'm putting sample code.
Main.xaml
<Page
x:Class="TempApp.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converter="using:TempApp"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ListView x:Name="MyList">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
<Setter Property="Margin" Value="0,-1,0,0" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Grid x:Name="ListItemDataTemplateGrid"
HorizontalAlignment="Stretch">
<Grid.Resources>
<converter:ValueToBackgroundConverter x:Key="ValueToBackgroundConverter" BackgroundColor="{Binding BgColor}" />
</Grid.Resources>
<Grid Background="{Binding Converter={StaticResource ValueToBackgroundConverter}}">
<!--Your Content-->
</Grid>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
Main.xaml.cs
public sealed partial class MainPage : Page
{
public List<TempList> ListDataSource = new List<TempList>();
public MainPage()
{
this.InitializeComponent();
FillList();
}
private void FillList()
{
ListDataSource.Clear();
ListDataSource.Add(new TempList { BgColor = "Red" });
ListDataSource.Add(new TempList { BgColor = "Red" });
MyList.ItemsSource = ListDataSource;
}
}
public class TempList
{
public string BgColor { get; set; }
}
ValueToBackgroundConverter.cs
class ValueToBackgroundConverter : DependencyObject, IValueConverter
{
public string BackgroundColor
{
get { return (string)GetValue(BackgroundColorProperty); }
set { SetValue(BackgroundColorProperty, value); }
}
public static readonly DependencyProperty BackgroundColorProperty =
DependencyProperty.Register("BackgroundColor",
typeof(string),
typeof(ValueToBackgroundConverter), null
);
public object Convert(object value, System.Type targetType, object parameter, string language)
{
//I've used static colors but you can do manipulations to convert string to color brush
if (BackgroundColor != null)
return new SolidColorBrush(Color.FromArgb(0xFF, 0xA3, 0xCE, 0xDC));
else
return new SolidColorBrush(Color.FromArgb(0xFF, 0xE3, 0xF0, 0xF4));
}
public object ConvertBack(object value, System.Type targetType, object parameter, string language)
{
throw new System.NotImplementedException();
}
}
I have created my own radiobutton to include index property as following:
public class IndexedRadioButton : RadioButton
{
public int Index { get; set; }
}
I am using this custom radio button in a list box:
<ListBox Name="myListBox" Grid.Row="1" VerticalAlignment="Top">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem" >
<Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<my:IndexedRadioButton Content="{Binding price}" GroupName="myGroup" IsChecked="{Binding isChecked}" Index="{Binding priceIndex}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Now, I want to fill this list box with values.
Code behind:
public MainClass()
{
public MainClass()
{
InitializeComponent();
string[] priceList = new string["1","2","3"];
List<MyClass> myClassList = new List<MyClass>();
for (int i = 0; i < priceList.Count; i++)
{
MyClass listClass = new MyClass()
{
price = response.priceList[i],
priceIndex = i,
isChecked = i==0?true:false
};
myClassList.Add(listClass);
}
myListBox.ItemsSource = myClassList;
}
private class MyClass
{
public string price {get; set;}
public int priceIndex {get; set;}
public bool isChecked { get; set; }
}
}
When I run the app, I get this error ->>{System.ArgumentException: Value does not fall within the expected range.} (No stack trace info)
What do you think is causing the error? There is no problem when I set some value to Index staticly in XAML Index="0" but there is a problem in binding Index="{Binding priceIndex}".
Thanks,
In order to allow bindings, you have to declare a Dependency Property. Try this:
public class IndexedRadioButton : RadioButton
{
public static readonly DependencyProperty IndexProperty = DependencyProperty.Register(
"Index",
typeof(int),
typeof(IndexedRadioButton),
null);
public int Index
{
get { return (int)GetValue(IndexProperty); }
set { SetValue(IndexProperty, value); }
}
}
You'll find more info here:
Dependency properties for Windows Phone