I have a WPF toolkit DataGrid that is bound to an observable collection of objects in the view model. In this DataGrid, I have defined a DataGridTemplateColumn for a certain field of that object. (Car.Name)
I'm trying to detect duplicates and set a certain style on the cell that already exists in another list of (similar) objects.
When this dialog gets loaded there is no selection. The IsDuplicate in the view model does get called for each item of the row, but I am unable to tell which item it's currently on in the view model. I thought of using CurrentItem, but it seems to always be null.
Question: How do I know in the View Model which current item is being called?
View XAML:
<toolkit:DataGrid ItemsSource="{Binding Cars}"
CurrentItem="{Binding CurrentCar}">
...
<toolkit:DataGridTemplateColumn.CellStyle>
<Style TargetType="{x:Type toolkit:DataGridCell}">
<Style.Triggers>
<DataTrigger Binding="{Binding
RelativeSource={RelativeSource FindAncestor,
AncestorType=toolkit:DataGrid},
Path=DataContext.IsDuplicate}" Value="False">
<Setter Property="BorderBrush" Value="Transparent" />
</DataTrigger>
<DataTrigger Binding="{Binding
RelativeSource={RelativeSource FindAncestor,
AncestorType=toolkit:DataGrid},
Path=DataContext.IsDuplicate}" Value="True">
<Setter Property="BorderBrush" Value="Red" />
<Setter Property="BorderThickness" Value="3" />
<Setter Property="ToolTip" Value="Duplicate" />
</DataTrigger>
</Style.Triggers>
</Style>
</toolkit:DataGridTemplateColumn.CellStyle>
ViewModel.cs:
public Car CurrentCar { get; set; }
public bool IsDuplicate
{
get
{
// Logic to check current car against a list of cars
var x = CurrentCar; // null
}
}
| Name | ...
| Car 1 | ... <-- Highlight
| Car 2 | ...
| Car 1 | ... <-- Highlight
You're thinking about it the wrong way. This shouldn't be an iterative method. IsDuplicate needs to be a property of Car, with a link to the collection so that each Car object determines if there are other items in the collection that match it.
public class Car
{
public Guid Id { get; set; }
public Collection<Car> Cars { get; set; }
public bool IsDuplicate
{
get
{
// Logic to check current car against a list of cars
return (Cars.Count(c => c.Id.Equals(this.Id))) > 1;
}
}
}
Then in XAML:
<toolkit:DataGridTemplateColumn.CellStyle>
<Style TargetType="{x:Type toolkit:DataGridCell}">
<Style.Triggers>
<DataTrigger Binding="IsDuplicate" Value="False">
<Setter Property="BorderBrush" Value="Transparent" />
</DataTrigger>
<DataTrigger Binding="IsDuplicate" Value="True">
<Setter Property="BorderBrush" Value="Red" />
<Setter Property="BorderThickness" Value="3" />
<Setter Property="ToolTip" Value="Duplicate" />
</DataTrigger>
</Style.Triggers>
</Style>
</toolkit:DataGridTemplateColumn.CellStyle>
Not so sure about the XAML binding syntax, that's just off the top of my head. But you get the idea.
Try:
<toolkit:DataGrid ItemsSource="{Binding Cars}"
SelectedItem="{Binding CurrentCar}">
Related
I have a customcontrol which render a textbox. I've also a style that set the color of the background based on some conditions as follows:
<Style x:Key="ArtParamStyle" TargetType="av:DC_Base">
<Setter Property="Background" Value="{StaticResource EditableAreaBrush}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Info.Upd.IsAutoCalc}" Value="True">
<Setter Property="Background" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=Forced}" Value="True">
<Setter Property="Background" Value="LightGreen" />
</DataTrigger>
</Style.Triggers>
</Style>
Initially as the value of my textbox is autocalculated, the background is correctly red. If I also set the Forced as true (by ticking a chebckbox) I've a weird result, the border of textbox is lightgreen but background not.
It seems to be a strange color, a combination of red and lightgreen. As test, if I set the "IsAutoCalc" color as Transparent, the trigger works correctly. How can I solve this?
your code seems to be correct. But I provide you my sample:
XAML:
<Window.Resources>
<Style x:Key="ArtParamStyle" TargetType="TextBox">
<Setter Property="Background" Value="Yellow" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Bool1}" Value="True"/>
<Condition Binding="{Binding Bool2}" Value="False"/>
</MultiDataTrigger.Conditions>
<Setter Property="Background" Value="Red" />
<Setter Property="BorderBrush" Value="Red" />
</MultiDataTrigger>
<DataTrigger Binding="{Binding Bool2}" Value="True">
<Setter Property="Background" Value="LightGreen" />
<Setter Property="BorderBrush" Value="LightGreen" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<StackPanel>
<TextBox Style="{StaticResource ArtParamStyle}" Height="50" Margin="4"/>
<CheckBox IsChecked="{Binding Bool1}"/>
<CheckBox IsChecked="{Binding Bool2}"/>
</StackPanel>
</Grid>
In this case I used Multidatatrigger to set red background when Bool2(your forced) is not checked.
MainWindow.cs:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChaged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
private bool bool1;
public bool Bool1
{
get { return bool1; }
set { bool1 = value; RaisePropertyChaged("Bool1"); }
}
private bool bool2;
public bool Bool2
{
get { return bool2; }
set { bool2 = value; RaisePropertyChaged("Bool2"); }
}
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
}
Probably your problem is related to your custom control.
I've extended Window to add some functionality, and part of this is the ability to specify a specific window size or allow it to size to the content. The codebehind looks like this, currently un-MVVMified.
public partial class DialogWindow : Window
{
public bool HasSize { get; set; }
public Size Size { get; set; }
}
The XAML then looks like this:
<Window ... Name="DialogWindowElement">
<Window.Style>
<Style TargetType="Window">
<Style.Triggers>
<DataTrigger Binding="{Binding HasSize, ElementName=DialogWindowElement}" Value="True">
<Setter Property="Width" Value="{Binding Size.Width, ElementName=DialogWindowElement}" />
<Setter Property="Height" Value="{Binding Size.Height, ElementName=DialogWindowElement}" />
</DataTrigger>
<DataTrigger Binding="{Binding HasSize, ElementName=DialogWindowElement}" Value="False">
<Setter Property="SizeToContent" Value="WidthAndHeight" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Style>
<ContentControl ...>
<!-- Content control using DataTemplates to determine content -->
</ContentControl>
</Window>
Resizing to content seems to work okay, but the specified width and height aren't applied. Any large content expands to all the size it needs instead of being constrained and then resizable later.
Snoop and other such tools imply the trigger is fired, but the setters don't seem to be having any effect.
Am I missing something here?
Edit: Added content control to the window to provide some more context
This works for me:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
public bool HasSize { get; set; } = true;
public Size Size { get; set; } = new Size(800, 800);
}
XAML:
<Window x:Class="WpfApplication1.Window21"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="Window1"
Name="DialogWindowElement">
<Window.Style>
<Style TargetType="Window">
<Style.Triggers>
<DataTrigger Binding="{Binding HasSize, ElementName=DialogWindowElement}" Value="True">
<Setter Property="Width" Value="{Binding Size.Width, ElementName=DialogWindowElement}" />
<Setter Property="Height" Value="{Binding Size.Height, ElementName=DialogWindowElement}" />
</DataTrigger>
<DataTrigger Binding="{Binding HasSize, ElementName=DialogWindowElement}" Value="False">
<Setter Property="SizeToContent" Value="WidthAndHeight" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Style>
<TextBlock Text="Test..." FontSize="40" FontWeight="Bold" />
</Window>
Make sure that you don't set the Width and Height properties of the window in your XAML because local values take take precedence over values set by style setters.
I have a datagrid on a wpf (mvvm) project.
the datagrid sort is showing the last item added to the collection as the first row.
i want to color the first row (in order to highlight new item added to the collection)
I've seen some similar questions about this manner but none of them are really related to what i am looking for.
i have tried to use a IValueConverter but it doesnt seems to be the right path for me as i need to get a unique identifier for the first row and change all the rest of the rows in order to classified it as a "First Row".
my object model for the items in the collection looks like this:
public class Messages
{
public string Date {get; set;}
public string Sender{get; set;}
public string Content{get; set;}
}
*EDIT
Forgot to add the converter code...
of course this will color all rows to red, as i dont know how to affect the other rows when the collection changes.
class DateToColorConverter : IValueConverter
{
object IValueConverter.Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (Convert.ToDateTime(value) >= DateTime.Now.AddMinutes(-1))
{
return "Red";
}
else
return "Yellow";
}
object IValueConverter.ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
You can use RelativeSource with Mode set to PreviousData to identify whether dataGrid row is first one or not. For first row PreviousData will return null.
Apply DataTrigger on DataGridRow in ItemContainerStyle:
<DataGrid>
<DataGrid.ItemContainerStyle>
<Style TargetType="DataGridRow">
<Setter Property="Background" Value="LightBlue"/>
<Style.Triggers>
<DataTrigger
Binding="{Binding RelativeSource={RelativeSource Mode=PreviousData}}"
Value="{x:Null}">
<Setter Property="Background" Value="Green"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.ItemContainerStyle>
</DataGrid>
You could do a simple solution if your data classes use some sort of an Id field. Let's say that newly added objects have an Id of -1, or 00000000-0000-0000-0000-000000000000 (to be updated/set upon a successful save). In this case, you could use a simple DataTrigger to change the Background of these items:
<DataTemplate DataType="{x:Type YourPrefix:Message}">
<Border>
<!--Define your Message UI here-->
<Border.Style>
<Style TargetType="{x:Type Border}">
<Setter Property="Background" Value="{StaticResource YourNormalBrush}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Id}" Value="-1">
<Setter Property="Background" Value="{StaticResource YourHighlightBrush}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
</DataTemplate>
Add a bool to your Model that will indicate the last element added :
public class Messages
{
public string Date {get; set;}
public string Sender{get; set;}
public string Content{get; set;}
public string IsNew {get; set;}
}
and set the style of the DataGridRow based on that Property :
<Window.Resources>
<Style x:Key="DataGridRowStyle" TargetType="DataGridRow">
<Setter Property="Background" Value="Blue"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsNew}" Value="True">
<Setter Property="Background" Value="Red"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
In code above the blue will be the default style and the Red is for the new Rows
<DataGrid ItemsSource="{Binding DataGridItems}" RowStyle="{StaticResource DataGridRowStyle}" AutoGenerateColumns="True">
</DataGrid>
<ItemsControl ItemsSource="{Binding Tariffs}" Margin="6">
<ItemsControl.ItemTemplate>
<DataTemplate>
<custControls:RoundButton Name="TariffButton" Margin="3"
Content="{Binding TariffName}" Style="{DynamicResource TariffButton}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cal:ActionMessage MethodName="PickUpTariff">
<cal:Parameter Value="{Binding Path=Content,
RelativeSource={RelativeSource AncestorType={x:Type custControls:RoundButton}}}" />
</cal:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</custControls:RoundButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Relative source binding works just fine. Content="{Binding TariffName}" works just fint to.
But here is what contained in the Style TariffButton.
<Style x:Key="TariffButton" TargetType="customControls:RoundButton" BasedOn="{StaticResource {x:Type Button}}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsChosen, Mode=OneWay}" Value="True">
<Setter Property="Background" Value="Green"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsChosen, Mode=OneWay}" Value="False">
<Setter Property="Background" Value="Blue"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
And it does not work! More accurately, it works just once. So the data trigger is invoked only once.
Here is the ViewModel part:
public class DocumentChoiceViewModel:PropertyChangedBase {
public ObservableCollection<Tariff> Tariffs { get; private set; }
public DocumentChoiceViewModel() {
Tariffs = new ObservableCollection<Tariff> {
new Tariff {IsChosen = true, TariffName = "ПАССАЖИРСКИЙ"},
new Tariff {IsChosen = false, TariffName = "ЭКСПРЕСС"},
new Tariff {IsChosen = false, TariffName = "КОМФОРТ"},
new Tariff {IsChosen = false, TariffName = "ДОП.СК.М-НОГИНСК"}
};
}
public void PickUpTariff(string tariffName) {
if (!IsTariffPlanExists(tariffName))
throw new InvalidOperationException(
"Unexpectable state. User should not be able to choose tariff plan which is not present in Tariffs list");
}
}
public class Tariff:PropertyChangedBase {
private bool isChosen;
public bool IsChosen {
get { return isChosen; }
set {
isChosen = value;
NotifyOfPropertyChange(()=>IsChosen);
}
}
public string TariffName { get; set; }
}
So first if you have some problems debugging bindings - use a converter as an additional layer where you will see all changes.
And second - set binding mode to be TwoWay.
Style Triggers only work with Dependency Properties defined in the TargetType.
Why don't you just make an IsChosenToBackgroundConverter and write this
<RoundButton Background="{Binding IsChosen, Converter={StaticResource IsChosenToBackgroundConverter}"/>
I don't know if this will fix your problem, but you don't need to add a Trigger for both True and False conditions:
<Style x:Key="TariffButton" TargetType="customControls:RoundButton"
BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Background" Value="Blue" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsChosen}" Value="True">
<Setter Property="Background" Value="Green" />
</DataTrigger>
</Style.Triggers>
</Style>
I have a TreeView with two levels, parents and children and I would like to get the value of a selected child. I used Josh Smith's TreeView with MVVM pattern to get me started and modified the IsSelected method to get the item that is selected but I'm always getting the parent item.
static object _selectedItem = null;
......
......
public bool IsSelected
{
get { return _isSelected; }
set
{
if (value != _isSelected)
{
_isSelected = value;
this.OnPropertyChanged("IsSelected");
{
_selectedItem = this;
}
}
}
}
Snippet of my XAML:
Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
<Setter Property="FontWeight" Value="Normal"/>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold"/>
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>