I have a list of items
public List<Item> MyItems { get; set; }
displayed on the DataGrid. One column shows status "icon" which is defined by template.
Code looks something like that:
Column template [...]
<DataGridTemplateColumn Header="Status">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid Height="18" Width="35">
<Rectangle Fill="{Binding Status.Background}" />
<TextBlock Text="{Binding Status.Text}" />
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Data model [...]
public class Item
{
public int ItemId { get; set; }
public string Name { get; set; }
public int StatusId { get; set; }
public Status Status { get; set; }
}
public class Status
{
public int StatusId { get; set; }
public int Text { get; set; }
public Brush Background
{
get
{
//Colour logic goes here
}
}
}
I'd like to remove colour logic from the data model and put it to the resource file instead.
<DataGridTemplateColumn Header="Status" CellTemplate="{StaticResource MyCustomTemplate}" </DataGridTemplateColumn>
I hope so far I am going to right direction but at this point I am lost as I don't know how to bind Status property (or StatusId) to MyCustomTemplate.
If anyone could help my with this it would be great.
EDIT
This works fine.
<DataGridTemplateColumn Header="V" Width="25" IsReadOnly="True" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<ic:CloseIcon Visibility="{Binding DockStatus, Converter={StaticResource CloseIconDisplayVisibilityConverter}}" />
<ic:DockIcon Visibility="{Binding DockStatus, Converter={StaticResource DockIconDisplayVisibilityConverter}}" />
<ic:UndockIcon Visibility="{Binding DockStatus, Converter={StaticResource UndockIconDisplayVisibilityConverter}}" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
This doesn't refresh UI when model changes (DockStatus changes)
<DataGridTemplateColumn Header="V" Width="25" IsReadOnly="True" CellEditingTemplateSelector="{StaticResource DockIconCellTemplateSelector}}">
If it's only about selecting the proper background colour for a specific item, or items with a specific StatusId, you could write a binding converter. The converter would simply convert an int to a Brush:
[ValueConversion(typeof(int), typeof(Brush))]
public class StatusColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is int)
{
int statusId = (int)value;
// create Brush from id here and return it
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
You would modify your Binding like this:
<Rectangle Fill="{Binding Status.StatusId, Converter={StaticResource StatusColorConverter}}" />
Related
Consider in my wpf application, I have a checkbox and 2 textedits, as below:
<CheckBox x:Uid="Checkbox_1" FlowDirection="RightToLeft" IsChecked="{Binding TickCheckBox, Mode=TwoWay}" Style="{StaticResource StandardCheckBoxStyle}">My Checkbox</CheckBox>
<dxe:TextEdit x:Uid="dxe:TextEdit_1" Grid.Row="1" Grid.Column="1" Width="100" Style="{StaticResource FleetScheduledHoursStyle}" EditValue="{Binding RealValue, Mode=OneWay}" EditMode="InplaceInactive" ToolTipService.ShowDuration="20000" />
<dxe:TextEdit x:Uid="dxe:TextEdit_2" Grid.Row="1" Grid.Column="1" Width="100" Style="{StaticResource FleetScheduledHoursStyle}" EditValue="{Binding RealValue, Mode=OneWay}" EditMode="InplaceInactive" ToolTipService.ShowDuration="20000" />
The TickCheckBox is bound to a property in my viewmodel as below:
private bool tickCheckBox;
public bool TickCheckBox
{
get
{
return this.tickCheckBox;
}
set
{
if (this.TickCheckBox.Equals(value))
{
return;
}
this.tiketCheckBox = value;
this.NotifyPropertyChange(() => this.TickCheckBox);
}
}
How do I change the property "EditMode" of one of the textedit (say Text_Edit1) to "InplaceActive" when I ticked the checkbox?
Thanks for your help!
You can use an IValueConverter:
BoolToEditModeConverte.cs
public class BoolToEditModeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (!(value is bool isChecked))
{
throw new ArgumentException("Converter value must be of type 'bool'");
}
return isChecked
? EditMode.InplaceInactive
: EditMode.None;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
Usage
<Window>
<Window.Resources>
<BoolToEditModeConverte x:Key="BoolToEditModeConverte" />
</Window.Resources>
<CheckBox x:Name="MyCheckbox" />
<TextEdit EditMode="{Binding ElementName=MyCheckBox,
Path=IsChecked,
Converter={StaticResource BoolToEditModeConverte}}" />
</Window>
Since you're using POCOViewModel, you just define a property for the TextEdit.EditMode and binding in the xaml, and define a method for the TickCheckBox changed event in the poco view model, like this:
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
this.DataContext = Vm.Create();
}
}
[POCOViewModel]
public class Vm {
public virtual bool TickCheckBox { get; set; } = false;
public virtual EditMode EditMode { get; set; } = EditMode.InplaceInactive;
public static Vm Create() => ViewModelSource.Create(() => new Vm());
protected void OnTickCheckBoxChanged() {
if (this.TickCheckBox) {
// or this.EditMode = EditMode.InplaceActive;
this.EditMode = EditMode.Standalone;
} else {
this.EditMode = EditMode.InplaceInactive;
}
}
}
and the xaml:
<StackPanel>
<CheckBox x:Uid="Checkbox_1" FlowDirection="RightToLeft" IsChecked="{Binding TickCheckBox, Mode=TwoWay}">
My Checkbox
</CheckBox>
<dxe:TextEdit
x:Uid="dxe:TextEdit_1"
EditMode="{Binding EditMode}"
EditValue="{Binding RealValue, Mode=OneWay}"
ToolTipService.ShowDuration="20000" />
<dxe:TextEdit
x:Uid="dxe:TextEdit_2"
EditMode="Standalone"
EditValue="{Binding RealValue, Mode=OneWay}"
ToolTipService.ShowDuration="20000" />
</StackPanel>
I have a class like this:
public class Person
{
public int PersonId { get; set; }
public string Name { get; set; }
public int AccountId { get; set; }
public Dictionary<int, List<string>> Values { get; set; }
}
I have a DataGrid in my XAML where I want to display the first index of the List<string> within the dictionary property Values as one of the column values, where the key being passed in will be the AccountId. The ItemSource of my DataGrid is a list of Person objects coming from my ViewModel, and the DataGrid has 3 columns, PersonId, Name, and Value (where value is the first index of the List<string> collection in the dictionary item)
I have seen examples on stackoverflow and other places on the internet that try to do this but none of the solutions worked for me.
Here is my XAML code:
<DataGrid Name="MyDataGrid" ItemsSource="{Binding Persons}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="ID">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding PersonId}" IsEnabled="False" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Name">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Name}" IsEnabled="False" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Value">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Values[{Binding AccountId}][0]}" IsEnabled="False" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
The last column is the column where I try to use a {Binding} as the key value, but it does not work. If I hard-code a valid AccountId, it works. Has anyone encountered this before?
Thanks!
one of view model purposes is to provide data to view in a convenient format. code to get value from dictionary by key and then return first item can be written in a view model, not in converter:
public class Person
{
public int PersonId { get; set; }
public string Name { get; set; }
public int AccountId { get; set; }
public Dictionary<int, List<string>> Values { get; set; }
public string AccountIdValue { get { return Values[AccountId].FirstOrDefault(); } }
}
then bind to that helper property:
<TextBox Text="{Binding AccountIdValue}" IsEnabled="False" />
I have two models in my application:
class Line
{
string line_id { get; set; }
string color { get; set; }
}
class Point
{
string point_id { get; set; }
string line_id { get; set; }
int weight { get; set; }
}
And I have two ObservableCollection:
lines - ObservableCollection<Line>
points - ObservableCollection<Point>
I want to display two ListBox'es : first (outer) to display lines, and second (inner) to display the points which belongs to this line.
<ListView x:Name="lvPoint" ItemsSource="{Binding}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding color, Mode=TwoWay}" />
<ListBox ItemsSource="{Binding SOMETHING, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding weight, Mode=OneWay}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I set DataContext for outer ListBox in the code:
lvPoint.DataContext = lines;
How can I set DataContext for inner ListBox to display Points for each Line ?
Your Line model is not good for this scenario. It should have a property such as called Points which contains the interested points belonging to the Line. Then the Binding is just simple:
class Line {
public string line_id { get; set; }
public string color { get; set; }
ObservableCollection<Point> _points;
public ObservableCollection<Point> Points {
get {
if (_points == null) _points = new ObservableCollection<Point>();
return _points;
}
}
}
Then in XAML code you can just set the Path to Points like this:
<ListBox ItemsSource="{Binding Points, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding weight, Mode=OneWay}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The model above is just an example showing the main idea. The full implementation should of course be different and more advanced depending on the current project.
Update: Without using the model above, you can try using Converter for the Binding. The Binding is set directly to the current item (Line). The Converter will convert Line to points (maybe based on line_id and your query method):
public class LineToPointsConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture){
var line = value as Line;
//convert to points by querying the points based on line.line_id here
return ...
}
public object ConvertBack(object value, Type targetType,
object parameter,
System.Globalization.CultureInfo culture){
return Binding.DoNothing;
}
}
Define a static property of LineToPointsConverter or create an instance of that converter in Resources:
<Window.Resources>
<local:LineToPointsConverter x:Key="lineToPointsConverter"/>
</Window.Resources>
Then in XAML code set that converter:
<ListBox ItemsSource="{Binding Mode=TwoWay, Path=.,
Converter={StaticResource lineToPointsConverter}}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding weight, Mode=OneWay}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I want to bind the selection change of the combo box to update my list box, but my xaml code is probably wrong.
This is my Collection that takes the data from the Service.
public class WorkersCollection
{
private WorkerClient client = new WorkerClient();
public ObservableCollection<Worker> Workers { get; set; }
public WorkersCollection()
{
Workers = new ObservableCollection<Worker>();
}
public ICollection<Worker> GetAllWorkers()
{
foreach (var worker in client.GetAllWorkers())
{
Workers.Add(worker);
}
return Workers;
}
}
My DataContext is workers:
public partial class MainWindow : Window
{
WorkersCollection workers;
public MainWindow()
{
InitializeComponent();
workers = new WorkersCollection();
this.DataContext = workers;
workers.GetAllWorkers();
}
}
and in XAML:
<ComboBox Name="cbxWorkers" HorizontalContentAlignment="Right" SelectedItem="{Binding Workers}" ItemsSource="{Binding Workers}">
<ComboBox.ItemTemplate>
<DataTemplate>
<ComboBoxItem Content="{Binding LastName}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ListBox Grid.Row="3" ItemTemplate="{StaticResource WorkersTemplate}" ItemsSource="{Binding ElementName=cbxWorkers, Path=SelectedItem}" />
How can I fix it?
ItemsSource property of class ListBox has type IEnumerable (msdn).
So you can't assign to it object of type Worker.
You can create converter to do that.
Converter class:
public class WorkerToListConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return new List<Worker> { value as Worker };
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
XAML code:
...
<Window.Resources>
<local:WorkerToListConverter x:Key="myCon" />
</Window.Resources>
...
<ComboBox Name="cbxWorkers" HorizontalContentAlignment="Right" ItemsSource="{Binding Workers}">
<ComboBox.ItemTemplate>
<DataTemplate>
<ComboBoxItem Content="{Binding LastName}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ListBox Grid.Row="3" ItemTemplate="{StaticResource WorkersTemplate}"
ItemsSource="{Binding ElementName=cbxWorkers, Path=SelectedItem, Converter={StaticResource myCon}}" />
...
You should also delete SelectedItem binding from ComboBox.
... SelectedItem="{Binding Workers}" ItemsSource="{Binding Workers}" ...
It's no sense to binding SelectedItem to the same thing as ItemsSource.
I'm very new to WPF so sorry if this is obvious but I can't seem to find any decent examples on the internet showing how it's done.
I have a DataGrid which is bound to a collection of DataItem called MyCollection. I want to create a generic DataTemplate that I can use for multiple columns in the grid (and elsewhere in the application should I need it).
E.g.
<DataGrid ItemsSource="{Binding MyCollection}" AutoGenerateColumns="False" SelectionUnit="Cell" EnableColumnVirtualization="True">
<DataGrid.Columns>
<DataGridTemplateColumn Header="File path" CellTemplate="{StaticResource FileSelectorEditorTemplate}" CellEditingTemplate="{StaticResource FileSelectorEditorTemplate}" />
<DataGridTemplateColumn Header="File path2" CellTemplate="{StaticResource FileSelectorEditorTemplate}" CellEditingTemplate="{StaticResource FileSelectorEditorTemplate}" />
<DataGridTemplateColumn Header="File path3" CellTemplate="{StaticResource FileSelectorEditorTemplate}" CellEditingTemplate="{StaticResource FileSelectorEditorTemplate}" />
...
My DataTemplate is defined at the moment in my Application resources as
<DataTemplate x:Key="FileSelectorEditorTemplate">
<Grid>
<TextBox Text="{Binding FilePath.PhysicalPath}" HorizontalAlignment="Stretch" Margin="0,0,35,0" />
<Button Content="..." Height="25" Width="25" Margin="0,0,5,0" HorizontalAlignment="Right" Click="FileOpen_Click" />
</Grid>
</DataTemplate>
Now the problem is that the binding is specified in the DataTemplate, whereas I need to apply a different binding for each of the properties FilePath, FilePath2, FilePath3 on the view model. I don't seem to be able to specify the Binding on the DataGridTemplateColumn though?
I'd appreciate any pointers in the right direction,
Thanks!
The binding on the DataGridTemplateColumn is specified in its CellTemplate.
If you want different bindings for the three columns, I'd say you would have to make a different DataTemplate for each of the columns. There might be some workaround, but I doubt it would be pretty.
Edit:
Having different templates you can use a DataTemplateSelector to select the right template for the current object.
Using IValueConverter (Just a quick sketch, but should work):
<DataTemplate x:Key="GenericTemplate" >
<TextBlock FontSize="14" >
<TextBlock.Text>
<Binding Converter="{StaticResource NewValue}" Path="Me" />
</TextBlock.Text>
</TextBlock>
</DataTemplate>
public class NewValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
someContainer obj = value as someContainer;
if (obj.type == MyType.First)
return (string)(obj.val1);
else
return (string)(obj.val2);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public enum MyType
{
First,
Second
}
public class someContainer
{
public someContainer Me { get; set; }
public string val1 { get; set; }
public string val2 { get; set; }
public MyType type;
public someContainer()
{
Me = this;
val1 = "string1";
val2 = "string2";
}
}
...
public ObservableCollection<someContainer> myList {get; set;}
...
<StackPanel Margin="0,10,0,0" Orientation="Vertical" Grid.Column="2">
<ItemsControl ItemsSource="{Binding MyList}" ItemTemplate="{StaticResource GenericTemplate}" />
</StackPanel>
If you can't use the option of Jesper Gaarsdal you might also be able to use a CellStyle and define the binding in the column-declaration.
see this SO for example: How to reuse WPF DataGridTemplateColumn (including binding)