Firstly, it's worth mentioning that I've looked at other similar topics, and they've helped me get this far but I need a little help getting over the finishing line.
The problem I'm having is that I can't get my DataTrigger to show the correct image, When the InPossesion bool flag is set to false I'm setting my enum property as IconImage2, which in turn should change the image in the datagrid to a red circle, this doesn't happen. If anyone could give me any pointers as to where I'm going wrong that would be great.
ViewModel Enum
public enum IconEnum
{
IconImage1,
IconImage2
}
public IconEnum MyIconEnumProperty
{
get { return _myEnum; }
set
{
_myEnum = value;
RaisePropertyChanged("MyIconEnumProperty");
}
}
ViewModel Method to load orders
private void LoadCloakroomOrders()
{
CloakroomOrderRepository repo = new CloakroomOrderRepository();
//Get All orders
var orders = repo.GetPublic();
foreach (var orderItem in orders)
{
Orders.Add(orderItem);
if (orderItem.InPossesion == false)
{
MyIconEnumProperty = IconEnum.IconImage2;
}
}
}
XAML
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Orders}"
SelectedItem="{Binding Path=SelectedCloakroomOrder}"
Margin="0,23,0,-0.5" Width="980" >
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Visibility="{Binding ShowIcon,
Converter={StaticResource BooleanToVisibilityConverter},
FallbackValue=hidden}" >
<Image.Style>
<Style TargetType="Image">
<Setter Property="Source" Value="/Resources/Images/circle_green.png"/>
<Style.Triggers>
<DataTrigger Binding="{Binding MyIconEnumProperty}" Value="IconImage2">
<Setter Property="Source" Value="/Resources/Images/circle_red.png"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Thanks!
Rather than having an enum in the VM, you could simply have an InPossesion property, and hide/show two images based on that. This keeps the view model cleaner, and the XAML clearer:
ViewModel:
public bool InPossession
{
get { return _inPossession; }
set { _inPossion = value; RaisePropertyChanged("InPossession"); }
}
private void LoadCloakroomOrders()
{
CloakroomOrderRepository repo = new CloakroomOrderRepository();
//Get All orders
var orders = repo.GetPublic();
foreach (var orderItem in orders)
{
Orders.Add(orderItem);
if (orderItem.InPossesion == false)
{
InPossession = false;
}
}
}
Converter:
public class BooleanToVisibilityConverter : IValueConverter
{
public Visibility VisibilitIfTrue { get;set; }
public Visibility VisibilitIfFalse { get;set; }
public BooleanToVisibilityConverter()
{
// Set default values for the most common usage
VisibilityIfTrue = Visible;
VisibilityIfFalse = Collapsed;
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// Converter could be extended to handle nullable bools as well, but ignore for now
if (value == null)
{
return DependencyProperty.UnsetValue;
}
// value should be of type bool
bool b = (bool)value;
if (b == true)
{
return VisibilityIfTrue;
}
else
{
return VisibilityIfFalse;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException(); // Not necessary
}
}
XAML:
<UserControl>
<UserControl.Resources>
<converters:BooleanToVisibilityConverter x:Key="TrueToVisibleConverter" VisibilityIfTrue="Visible" VisibleIfFalse="Collapsed"/>
<converters:BooleanToVisibilityConverter x:Key="FalseToVisibleConverter" VisibilityIfTrue="Collapsed" VisibleIfFalse="Visible"/>
</UserControl.Resources>
</UserControl>
...
<DataTemplate>
<Grid Visibility="{Binding ShowIcon, FallbackValue=hidden}">
<Image Source="/Resources/Images/circle_green.png" Visibility="{Binding InPossession, Converter={StaticResource TrueToVisibleConverter}}"/>
<Image Source="/Resources/Images/circle_red.png" Visibility="{Binding InPossession, Converter={StaticResource FalseToVisibleConverter}}"/>
</Grid>
</DataTemplate>
Try also specify the enum type.
Value="{x:Static wpf:IconEnum.IconImage2}"
wpf: is a namespace like xmlns:wpf="clr-namespace:Sandbox.WPF" in my case. But I would probably go for another solution, like sondergard suggests, it's much cleaner style than this hacking.
Related
I am new in wpf and mvvm databinding. Now i am trying to make crud process with data gird. I have a problem to updateing process.I want to get update value after data grid cell updated by mvvm process.
Model
public class EmployeeType : INotifyPropertyChanged
{
string _EmpType;
public string EmpType
{
get
{
return _EmpType;
}
set
{
if(_EmpType !=value)
{
_EmpType = value;
RaisePropertyChange("EmpType");
}
}
}
string _EmpTypeDesc;
public string EmpTypeDesc
{
get
{
return _EmpTypeDesc;
}
set
{
if(_EmpTypeDesc!=value)
{
_EmpTypeDesc = value;
RaisePropertyChange("EmpTypeDesc");
}
}
}
bool _OTRounding;
public bool OTRounding
{
get
{
return _OTRounding;
}
set
{
if(_OTRounding!=value)
{
_OTRounding = value;
RaisePropertyChange("OTRounding");
}
}
}
decimal _EarlyOTTimeBuffer;
public decimal EarlyOTTimeBuffer
{
get
{
return _EarlyOTTimeBuffer;
}
set
{
if(_EarlyOTTimeBuffer!=value)
{
_EarlyOTTimeBuffer = value;
RaisePropertyChange("EarlyOTTimeBuffer");
}
}
}
string _EarlyOTRounding;
public string EarlyOTRounding
{
get
{
return _EarlyOTRounding;
}
set
{
if(_EarlyOTRounding!=value)
{
_EarlyOTRounding = value;
RaisePropertyChange("EarlyOTRounding");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
void RaisePropertyChange(string prop)
{
if(PropertyChanged !=null)
{
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
View Model
class EmployeeTypeViewModel:ViewModelBase
{
private ObservableCollection<EmployeeType> _EmployeeTypeList = new ObservableCollection<EmployeeType>();
private ObservableCollection<TimeFormat> _ThreeTimeFormat = new ObservableCollection<TimeFormat>();
public ObservableCollection<TimeFormat> ThreeTimeFormat
{
get
{
return _ThreeTimeFormat;
}
set
{
_ThreeTimeFormat = value;
RaisePropertyChanged("ThreeTimeFormat");
}
}
public ObservableCollection<EmployeeType> EmployeeTypeList
{
get
{
return _EmployeeTypeList;
}
set
{
_EmployeeTypeList = value;
RaisePropertyChanged("EmployeeTypeList");
}
}
public EmployeeType _SelectedEarlyOTRounding;
public EmployeeType SelectedEarlyOTRounding
{
get
{
return _SelectedEarlyOTRounding;
}
set
{
if (_SelectedEarlyOTRounding != value)
{
_SelectedEarlyOTRounding = value;
RaisePropertyChanged("SelectedEarlyOTRounding");
}
}
}
public EmployeeTypeViewModel()
{
_EmployeeTypeList = DataAccess.EmployeeTypeDataAccessor.GetAllEmployeeTypes();
ThreeTimeFormat = TMSHelper.GetThreeTimeFormat();
}
}
View
<UserControl.Resources>
<ViewModels:EmployeeTypeViewModel x:Key="ViewModel"/>
</UserControl.Resources>
<Grid DataContext="{Binding Source={StaticResource ViewModel}}">
<DataGrid Margin="10,10,9.6,10.2" x:Name="empgrid" ItemsSource="{Binding EmployeeTypeList,Mode=TwoWay}" AutoGenerateColumns="False" >
<DataGrid.Columns>
<DataGridTextColumn Header="EmpType" Binding="{Binding EmpType,Mode=TwoWay}"/>
<DataGridCheckBoxColumn Header="OTRounding" Binding="{Binding OTRounding}"/>
<DataGridTextColumn Header="Description" Binding="{Binding EmpTypeDesc}"/>
<DataGridTextColumn Header="Early OT Time Buffer" Binding="{Binding EarlyOTTimeBuffer}"/>
<DataGridTemplateColumn Header="Early OT Time Rounding">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox SelectedValuePath="Value" DisplayMemberPath="Key" ItemsSource="{Binding Path=DataContext.ThreeTimeFormat,ElementName=empgrid}" SelectedValue="{Binding EarlyOTRounding,Mode=TwoWay}" SelectedItem="{Binding SelectedEarlyOTRounding}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
I just realized things is if I changed value in datagrid cell,It's will auto update the EmployeeTypeList in viewmodel.Because i added mode=twoway in itemsource of grid.right? But when i debug, Set of EmployeeTypeList never happen.Why? if my doing process is wrong,please let me known how to do that? If you don't understand,please let me known.Thanks.
You probably just don't understand to binding completly at this point and it is okay.
Mode=TwoWay means that binded property will be changed on the UI when value is changed in the underlying object and also when user change the value on the UI.
In your case you should have to replace collection on the UI to notice the change. So, far you are changing content of the ObservableCollection and because of that you are not getting any notification on collection level. You should have get notification for EmpType when you change it on the UI.
Is it clear?
One of the BindingMode values. The default is Default, which returns
the default binding mode value of the target dependency property.
However, the default value varies for each dependency property. In
general, user-editable control properties, such as those of text boxes
and check boxes, default to two-way bindings, whereas most other
properties default to one-way bindings.
(source: MSDN )
so generally where it is possible to edit usually does not need to inform
now, for your combobox to work correctly try this.
I usually use it and works perfectly
<DataGridComboBoxColumn Header="Early OT Time Rounding"
DisplayMemberPath="Key"
SelectedValuePath="Value"
SelectedValueBinding="{Binding EarlyOTRounding, UpdateSourceTrigger=PropertyChanged}"
>
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}, Path=DataContext.ThreeTimeFormat, UpdateSourceTrigger=PropertyChanged}"/>
<Setter Property="Width" Value="280" />
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}, Path=DataContext.ThreeTimeFormat, UpdateSourceTrigger=PropertyChanged}"/>
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
attention to this
DataGridComboBoxColumn must have a SelectedValueBinding property set to being an item in your EmployeeTypeList so that the selected value is changed in the list
in your case:
SelectedValueBinding="{Binding EarlyOTRounding, UpdateSourceTrigger=PropertyChanged}"
I want to bind a TextBlock to an ObservableCollection<Log>. Each Log has different Type (enum), each Type should be resulting in different Run's Foreground (e.g. Error is Red, Success is Green).
I've done a quick read to these Q&A:
WPF and ObservableCollection<T>
In WPF how to define a Data template in case of enum?
WPF DataTemplate Binding depending on the type of a property
But my mind got stuck, because, I'm very new to WPF.
This is the class Log and enum Type:
public enum Type
{
Start = 1,
Stop = 0,
Info = 2,
Success = 4,
Error = 8
};
public class Log
{
public Type Type { get; set; }
public string Message { get; set; }
}
...and this is how I created the collection:
public partial class MainWindow : Window
{
ObservableCollection<Log> mLogCollection = new ObservableCollection<Log>();
public ObservableCollection<Log> LogCollection
{
get { return mLogCollection; }
}
public MainWindow()
{
DataContext = this;
mLogCollection.Add(new Log { Type = Log.Type.Error, Message = "Operation failed" });
mLogCollection.Add(new Log { Type = Log.Type.Success, Message = "Operation complete" });
}
How do I make everything like I want so it will be resulting in something like this?:
<TextBlock>
<Run Text="Operation failed" Foreground="Red"/>
<Run Text="Operation complete" Foreground="Green"/>
</TextBlock>
A balanced XAML and code behind solution is preferred rather than just full XAML.
I'm sorry if my explanation is not clear enough, I'm a bit sleepy right now.
Any help would be appreciated.
Here's the easiest way:
<StackPanel Orientation="Vertical">
<ItemsControl
ItemsSource="{Binding LogCollection}"
>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="local:Log">
<TextBlock
Text="{Binding Message}"
x:Name="MessageText"
/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Type}" Value="Error">
<Setter TargetName="MessageText" Property="Foreground" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding Type}" Value="Success">
<Setter TargetName="MessageText" Property="Foreground" Value="Green" />
</DataTrigger>
<!--
Etc. for the other log type values.
-->
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
But that's pure XAML and you want code behind. #Evk suggested a value converter for the foreground type, and that's a reasonable way to do it.
XAML:
<ItemsControl
ItemsSource="{Binding LogCollection}"
>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="local:Log">
<DataTemplate.Resources>
<local:LogTypeBrushConverter
x:Key="LogTypeBrush"
/>
</DataTemplate.Resources>
<TextBlock
Text="{Binding Message}"
Foreground="{Binding Type, Converter={StaticResource LogTypeBrush}}"
/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
C#. You defined your enum as Log.Type which won't compile, because the Log class has a property named Type. So I renamed the enum to LogType.
public class LogTypeBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Color color = Colors.Black;
switch ((LogType)value)
{
case LogType.Start:
color = Colors.DodgerBlue;
break;
case LogType.Stop:
color = Colors.OrangeRed;
break;
case LogType.Info:
color = Colors.Blue;
break;
case LogType.Success:
color = Colors.Green;
break;
case LogType.Error:
color = Colors.Red;
break;
}
return new SolidColorBrush(color);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Incidentally, DataContext = this is a bad idea in general. On a Window it's relatively harmless, but try it on a UserControl and you start needing bizarre wokarounds. You can leave that out and bind to LogCollection like so:
ItemsSource="{Binding LogCollection, RelativeSource={RelativeSource AncestorType=Window}}"
i am making a simple list with few options.
This is how it looks like:
Buttons appear when hit the listviewitem and disappear when leave
When press Play, then this button stays Visible and Content changes to Stop
My problems is:
When i press Stop, then this Button stays Visible and Triggers disappear :/
What else i want to do, but i can't is:
When i press Play then Slider appears, otherwise Collapsed
I hope that someone can help me.
My code looks like this so far:
XAML:
<ListView Name="lst">
<ListView.View>
<GridView>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button Name="btnDownload" Content="Download" Visibility="Hidden" MinWidth="100"/>
<Button Name="btnPlay"
Click="btnPlay_Click"
Content="Play"
Visibility="Hidden"
MinWidth="100"/>
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding
RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType={x:Type ListViewItem}},
Path=IsMouseOver}"
Value="True">
<Setter TargetName="btnDownload"
Property="Visibility"
Value="Visible"/>
<Setter TargetName="btnPlay"
Property="Visibility"
Value="Visible"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn>
<GridViewColumn.Header>
<GridViewColumnHeader Tag="Name">Name</GridViewColumnHeader>
</GridViewColumn.Header>
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel MinWidth="200">
<TextBlock Text="{Binding Name}"/>
<Slider Name="Slider" Visibility="Visible"/>
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
CS:
public partial class MainWindow : Window
{
public ListCollectionView MyCollectionView { get; set; }
public ObservableCollection<Songs> songs = new ObservableCollection<Songs>();
public MainWindow()
{
InitializeComponent();
MyCollectionView = new ListCollectionView(songs);
lst.ItemsSource = MyCollectionView;
songs.Add(new Songs(){Name = "Eminem - Superman"});
songs.Add(new Songs(){Name = "Rihanna - Please don't stop the music"});
songs.Add(new Songs(){Name = "Linkin Park - Numb"});
}
private void btnPlay_Click(object sender, RoutedEventArgs e)
{
//Reset all songs
List<Button> buttons = FindVisualChildren<Button>(lst).ToList();
foreach (Button button in buttons)
{
button.Content = "Play";
//Loosing Triggers
}
//Play current
Button btn = sender as Button;
btn.Visibility = Visibility.Visible;
btn.Content = "Stop";
}
private IEnumerable<T> FindVisualChildren<T>(DependencyObject obj) where T : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child != null && child is T)
{
yield return (T)child;
}
else
{
var childOfChild = FindVisualChildren<T>(child);
if (childOfChild != null)
{
foreach (var subchild in childOfChild)
{
yield return subchild;
}
}
}
}
}
}
public class Songs : INotifyPropertyChanged
{
private string name;
public string Name
{
get { return name; }
set
{
if (name != value)
{
name = value;
NotifyPropertyChanged("Name");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
Project:
MusicList.sln
And ofcourse - sorry for my bad english :)
You could create a value converter that would take the text of your button and return a visibility value based on the text value. Something like this;
public class StringToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var buttonText = (string) value;
switch (buttonText.ToLower())
{
case "stop":
return Visibility.Visible;
default:
return Visibility.Collapsed;
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Create an instance of this class in your xaml by creating a static resource and adding it into your resource dictionary e.g. something like this;
<Window.Resources>
<myNamespace:StringToVisibilityConverter x:Key="StringToVisibilityConverter"/>
</Window.Resources>
Then bind your slider visibility to your button text;
<Slider Name="Slider" Visibility="{Binding ElementName=btnPlay, Path=Content, Converter={StaticResource StringToVisibilityConverter}}"/>
I am trying to link the visibility of a TextBlock to a bool property which is also linked to a checkbox using WPF and c#. I have the following code in two different sections of the same xaml file (one section is a summary, and the other is settings. I am very new to WPF, and am learning as I go. Currently, the TextBlock is visible no matter what the value of IsSecondaryMessageFilePath is.
<TextBlock Name="secondaryfolderinfo" Foreground="Red">
<ContentControl Content="Secondary message folder" Foreground ="Black" />
<ContentControl Content = "{Binding Path=SecondaryMessageFilePath}" ContentStringFormat="" ClipToBounds="False"></ContentControl>
<ContentControl Content = " "></ContentControl>
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsSecondaryMessageFilePath}" Value="True">
<Setter Property="Visibility" Value="Visible"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
Further down I have:
<CheckBox IsChecked="{Binding Path=IsSecondaryMessageFilePath, Mode=TwoWay}"
Name="SecondaryPathCheckBox"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Margin="320,7,0,0">Save additional locations</CheckBox>
Finally, in the code-behind, I have:
public bool IsSecondaryMessageFilePath
{
get { return _isSecondaryMessageFilePath; }
set
{
if (_isSecondaryMessageFilePath != value)
{
_isSecondaryMessageFilePath = value;
OnPropertyChanged("IsSecondaryMessageFilePath");
}
}
}
private bool _isSecondaryMessageFilePath;
public string SecondaryMessageFilePath
{
get { return _secondaryMessageFilePath; }
set
{
if (_secondaryMessageFilePath != value)
{
_secondaryMessageFilePath = value;
OnPropertyChanged("SecondaryMessageFilePath");
}
}
}
private string _secondaryMessageFilePath;
Any assistance would be appreciated.
EDIT
Working from the suggestion below, I tried adding the BooleanToVisibilityConverter, but am getting a missing assembly reference for it, and am to new to WPF to figure out how to resolve it. My opening code is as follows:
<UserControl x:Class="Sender_Receiver.SenderReceiverSetup"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:diagnostics="clr-namespace:System.Diagnostics;assembly=WindowsBase"
xmlns:m=...
xmlns:
<UserControl.Resources>
<BooleanToVisibiltyConverter x:Key="BooleanToVisibilityConverter"/>
...
Your code looks ok at first glance, but you really don't need to use a data trigger for this. WPF comes with a BooleanToVisibilityConverter class that you declare in your resources:
<BooleanToVisibiltyConverter x:Key="BooleanToVisibilityConverter"/>
Then in your TextBlock, you bind Visibility:
<TextBlock Visibility="{Binding Path=IsSecondaryMessageFilePath, Converter={StaticResource BooleanToVisibilityConverter}}"/>
Just so you know, there may be a simpler way to do this, just bind to the IsChecked property itself!
<CheckBox x:Name="UseSecondaryPath"/>
<TextBlock Visibility="{Binding ElementName=UseSecondaryPath, Path=IsChecked, Converter={StaticResource BooleanToVisibilityConverter}}"/>
Of course if you need the bool for something else that wouldn't be an ideal solution, but it is a little cleaner if its just for the UI.
The code for a custom BooleanToVisibilityConverter, if you are interested, is:
public class BooleanToVisibilityConverter : IValueConverter
{
public object Convert (object value, ...)
{
if ((bool)value)
return Visibility.Visible;
else
return Visibility.Collapsed;
}
public object ConvertBack(object value, ...)
{
return Binding.DoNothing;
}
}
Let me know if I can clarify anything or assist further.
private Boolean _IsChecked;
//Bind this to your checkbox
public Boolean IsChecked
{
get { return _IsChecked; }
set { _IsChecked= value; OnPropertyChanged("IsChecked"); OnPropertyChanged("TextBoxVis"); }
}
//Bind this to your TextBox's Visibility Property
public Visibility TextBoxVis
{
get { return IsChecked ? Visibility.Visible : Visibility.Collapsed; }
}
I bind the Rectangle Fill property on bool value (Fill="{Binding Path=IsSelected, Converter={StaticResource rectangleFillConverter}}") and it throws a null exception. I checked the value of property (IsSelected) is not null. When I remove Converter from Fill property it works. Here is my code:
xaml
<Rectangle Width="{Binding Duration}" Height="20" Tag="{Binding .}" IsEnabled="{Binding Path=IsEnabled}" Fill="{Binding Path=IsSelected, Converter={StaticResource rectangleFillConverter}}" ToolTip="{Binding Path=Shift}" MouseDown="LabelShift_MouseDown"></Rectangle>
Converter
public class RectangleControlFillConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
SolidColorBrush brush;
bool b= (bool)value;
if (b)
brush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#5C8FFF"));
else
brush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#73E34D"));
return brush;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Static Resource
<converters:RectangleControlFillConverter x:Key="rectangleFillConverter"/>
Property
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
OnPropertyChanged("IsSelected");
}
}
ItemsControl where is rectangle and this converter works Converter={StaticResource timeToPositionConverter}}"
<ItemsControl Name="icSchedule" ItemsSource="{Binding .}" Grid.Row="1" Grid.Column="1">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Margin="0"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding Path=icw}" Tag="{Binding .}" Margin="0,10,0,0"><!--Margin="3"Grid.Row="1" Grid.Column="1" Background="DarkGray" BorderThickness="1" BorderBrush="White"-->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas IsItemsHost="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="{x:Type ContentPresenter}">
<Setter Property="Canvas.Left" Value="{Binding Path=Start, Converter={StaticResource timeToPositionConverter}}" />
<Setter Property="Canvas.Top" Value="{Binding Path=Index}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="TimeLineEntry">
<Border BorderThickness="1" BorderBrush="DarkGray">
<Rectangle Width="{Binding Duration}" Height="20" Tag="{Binding .}" IsEnabled="{Binding Path=IsEnabled}" Fill="{Binding Path=IsSelected, Converter={StaticResource rectangleFillConverter}}" ToolTip="{Binding Path=Shift}" MouseDown="LabelShift_MouseDown">
</Rectangle>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
This is the class on wich object is rectangle binding (icw is a list of that objects)
public partial class ScheduleItem
{
public string Shift
{
get
{
//string s = ((DateTime)DateFrom).ToString("dd.MM.yyyy") + " " + ((TimeSpan)TimeFrom).ToString() + " - " + ((DateTime)DateTo).ToString("dd.MM.yyyy") + " " + ((TimeSpan)TimeTo).ToString();
String s = String.Format("{0}.{1} {2}:{3} - {4}.{5} {6}:{7}", FullDateFrom.Day, FullDateFrom.Month, FullDateFrom.Hour, FullDateFrom.Minute, FullDateTo.Day, FullDateTo.Month, FullDateTo.Hour, FullDateTo.Minute);
return s;
}
}
private DateTime FullDateFrom
{
get
{
DateTime dt = ((DateTime)DateFrom).AddHours(((TimeSpan)TimeFrom).Hours).AddMinutes(((TimeSpan)TimeFrom).Minutes);
return dt;
}
}
private DateTime FullDateTo
{
get
{
DateTime dt = ((DateTime)DateTo).AddHours(((TimeSpan)TimeTo).Hours).AddMinutes(((TimeSpan)TimeTo).Minutes);
return dt;
}
}
public string Name { get; set; }
public DateTime Start { get { return FullDateFrom; } }
private int index;
public int Index
{
get
{
if (CampaignPerson != null)
return CampaignPerson.Index;
else
return index;
}
set
{
index = value;
}
}
public int Duration
{
get
{
TimeSpan dt = FullDateTo - FullDateFrom;
return (dt.Days* 92) + (dt.Hours*4);
}
}
public bool IsEnabled
{
get { return (FullDateFrom > DateTime.Now); }
}
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
OnPropertyChanged("IsSelected");
}
}
#region setters
partial void OnTimeFromChanged()
{
OnPropertyChanged("Duration");
OnPropertyChanged("Start");
}
partial void OnTimeToChanged()
{
OnPropertyChanged("Duration");
OnPropertyChanged("Start");
}
partial void OnDateFromChanged()
{
OnPropertyChanged("Duration");
OnPropertyChanged("Start");
}
partial void OnDateToChanged()
{
OnPropertyChanged("Duration");
OnPropertyChanged("Start");
}
#endregion
}
ViewModel Class The icShedule is Binding on collection of this Class
public class ScheduleExtension
{
public ICollectionView icw {get; set;}
public ScheduleExtension(CampaignPerson cp)
{
campainPerson = cp;
scheduleItemsList.CollectionChanged += new NotifyCollectionChangedEventHandler(_scheduleItemsList_CollectionChanged);
icw = CollectionViewSource.GetDefaultView(scheduleItemsList);
icw.Filter = ScheduleFilter;
}
private CampaignPerson _campainPerson;
public CampaignPerson campainPerson
{
get { return _campainPerson; }
set
{
_campainPerson = value;
scheduleItemsList = new ObservableCollection<ScheduleItem>(_campainPerson.ScheduleItem.Where(p=>p.active));
}
}
private DateTime _currentDate;
public DateTime CurrentDate
{
get { return _currentDate; }
set
{
_currentDate = value;
icw.Refresh();
//CurrrentScheduleItemsList = new ObservableCollection<ScheduleItem>(scheduleItemsList.Where(p => p.active && ((DateTime)p.DateFrom).Month == CurrentDate.Month && ((DateTime)p.DateFrom).Year == CurrentDate.Year));
//OnPropertyChanged("CurrentScheduleItemsList");
}
}
public ObservableCollection<ScheduleItem> scheduleItemsList;
void _scheduleItemsList_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
}
private bool ScheduleFilter(object item)
{
if (CurrentDate != null)
{
ScheduleItem si = item as ScheduleItem;
return (CurrentDate.Month == ((DateTime)si.DateFrom).Month && CurrentDate.Year == ((DateTime)si.DateFrom).Year);
}
return false;
}
}
Try changing the <DataTemplate DataType="TimeLineEntry"> to <DataTemplate DataType="ScheduleItem">. Maybe ScheduleItem is a TimeLineEntry (you posted only one part of a partial class), but try if it works.
The problem could also be caused by the way you are handling the collection. In the constructor you say:
icw = CollectionViewSource.GetDefaultView(scheduleItemsList);
icw.Filter = ScheduleFilter;
And then in the campainPerson property you say:
scheduleItemsList = new ObservableCollection<ScheduleItem>(_campainPerson.ScheduleItem.Where(p=>p.active));
You have to either use the same collection object (by clearing it and adding new items), or by creating new ICollectionView and assigning it to icw (so you'd have to repeat the two lines from constructor after creating new ObservableCollection). Try that too.
I dont think problem is from converter, even if all work fine when you remove it. This is my test app.
MainWindow.xaml
<Window x:Class="MVVMTests.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
xmlns:local="clr-namespace:MVVMTests"
Background="{StaticResource {x:Static SystemColors.ControlBrushKey}}"
>
<Window.Resources>
<ResourceDictionary>
<local:RectangleControlFillConverter x:Key="rectangleFillConverter" />
</ResourceDictionary>
</Window.Resources>
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<CheckBox Content="toto" IsChecked="{Binding IsSelected}" />
<Rectangle Fill="{Binding Path=IsSelected, Converter={StaticResource rectangleFillConverter}}" Grid.Row="1" />
</Grid>
</Window>
The ViewModel
namespace MVVMTests
{
public class MainWindowViewModel : NotificationObject
{
private Boolean isSelected;
public MainWindowViewModel()
{
isSelected = true;
}
public Boolean IsSelected
{
get { return this.isSelected; }
set
{
this.isSelected = value;
this.RaisePropertyChanged<Boolean>(() => IsSelected);
}
}
}
}
and the conveter
namespace MVVMTests
{
[ValueConversion(typeof(Boolean), typeof(SolidColorBrush))]
public class RectangleControlFillConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
SolidColorBrush brush;
bool b = (bool)value;
if (b)
brush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#5C8FFF"));
else
brush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#73E34D"));
return brush;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
With this simple code, we can see that the converter is not the problem (window switch ugly blue / ugly green), and binding works fine.
That's not a solution, but that's show us the problem is elsewhere ...
Converter in rectangle still doesnt work on any property.
This is working for me Fill="{Binding Path=CurrentColor}"
and this change in ScheduleItem
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
if(_isSelected)
CurrentColor = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#73E34D"));
else
CurrentColor = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#5C8FFF"));
OnPropertyChanged("CurrentColor");
}
}
private SolidColorBrush _currentColor = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#5C8FFF"));
public SolidColorBrush CurrentColor
{
get { return _currentColor; }
set { _currentColor = value; }
}
Thanks guys for your time and your suggestion.