Converter not firing after collection update - c#

I have ran into a issue with the converters... They are not triggering once the bound collection is updated although they trigger when the collection is first populated. I would like to have them fire whenever there is a change in the collection.
So far I have built a simple converter:
public class TableConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
VM.Measurement t = ((VM.Measurement)((TextBlock)value).DataContext);
if (t.Delta != null)
{
if (Math.Abs((double)t.Delta) < t.Tol)
return "Green";
else
return "Red";
}
else
return "Red";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
which is linked to a style
<conv:TableConverter x:Key="styleConvStr"/>
<Style x:Key="CellStyleSelectorTol" TargetType="syncfusion:GridCell">
<Setter Property="Background" Value="{Binding RelativeSource={RelativeSource Self}, Path=Content, Converter={StaticResource styleConvStr}}" />
</Style>
Which is used in this DataGrid
<syncfusion:SfDataGrid x:Name="CheckGrid" BorderBrush="White" Grid.Row="1" Grid.Column="1" AllowEditing="True" ItemsSource="{Binding ChecksList, Mode=TwoWay}" Background="White" SnapsToDevicePixels="False"
ColumnSizer="None" AllowResizingColumns="False" AllowTriStateSorting="True" AllowDraggingColumns="False" CurrentCellEndEdit="CheckGrid_CurrentCellEndEdit" AutoGenerateColumns="False"
NavigationMode="Cell" HeaderRowHeight="30" RowHeight="21" GridPasteOption="None" Margin="20 10 10 10" AllowGrouping="True" SelectedItem="{Binding SelectedLine, Mode=TwoWay}"
SelectionUnit="Row" SelectionMode="Single" RowSelectionBrush="#CBACCB" VirtualizingPanel.IsVirtualizing="True" Visibility="Visible">
<syncfusion:GridTextColumn Width="100" ColumnSizer="SizeToCells" AllowEditing="True" MappingName="Measured" CellStyle="{StaticResource CellStyleSelectorTol}" HeaderText="Measured" TextAlignment="Center" AllowFiltering="False" FilterBehavior="StringTyped"/>
The VM contains an Observable Collection which implements NotifyPropertyChanged all the way down to the Measurement Class. The properties fire up nicely so it is not a binding issue.
private ObservableCollection<Measurement> _checkList = new ObservableCollection<Measurement>();
public ObservableCollection<Measurement> ChecksList
{
get
{
return _checkList;
}
set
{
_checkList = value;
NotifyPropertyChanged();
}
}
Any help with this would be greatly appreciated.
Thanks
EDIT:
Here is the code that updates the collection. Apologies for it being quite messy. Lineitem is the selected line for which Measured and Delta are updated. These are properly displayed in the grid once modified.
public void NewMeasurement(VM.Measurement measurementShell)
{
using (VMEntity DB = new VMEntity())
{
var Check = CheckSets.Where(x => x.ID == SelectedLine.ID).First();
if (Check.Measurement == null)
{
Check.Measurement = measurementShell.Index;
var Lineitem = ChecksList.Where(x => x.ID == SelectedLine.ID).First();
var measurement = DB.Measurements.Where(x => x.Index == Check.Measurement).First();
Lineitem.Measured = (double)measurement.measurement1;
Lineitem.Delta = Lineitem.Measured - Lineitem.Target;

OK, it looks like the problem is that you are changing properties of the cell content item (LineItem, in the NewMeasurement() method), but it's still the same object, so the cell's content doesn't change. The cell's Content is the source for the binding. If that doesn't change, the binding won't wake up and update the target. You're raising PropertyChanged, but this particular binding has no way of knowing you want it to listen to this object for those property changes. Easy enough fix: We'll start telling it exactly what to listen for.
Fortunately the solution means simplifying some of your code. Passing a UI control into a value converter is exotic and not necessary.
What you care about in the converter is Measurement.Delta and Measurement.Tol. When either one changes, the Binding should update its target. You don't want to do that in a clever way. You just want a Binding for each one. That's a Binding's job.
So tell the Binding that you care about those properties, and rewrite the converter to accept both of them as parameters.
<Style x:Key="CellStyleSelectorTol" TargetType="syncfusion:GridCell">
<Setter
Property="Background"
>
<Setter.Value>
<MultiBinding Converter="{StaticResource styleConvStr}">
<Binding Path="Delta" />
<Binding Path="Tol" />
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
Converter:
public class TableConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
// I'm inferring that Measurement.Delta is Nullable<double>; if that's
// not the case, change accordingly. Is it Object instead?
double? delta = (double?)values[0];
double tol = (double)values[1];
if (delta.HasValue && Math.Abs(delta.Value) < tol)
{
return "Green";
}
return "Red";
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

Related

WPF Change/modify color saturation/brightness of an Image by Trigger or Behavior [duplicate]

I have multibinding on Image control. I bind two properties one is type of bool(IsLogged) and one is typeof Uri (ProfilePhoto).
XAML:
<Image.Source >
<MultiBinding Converter="{StaticResource avatarConverter}">
<Binding Path="ProfilePhoto"></Binding>
<Binding Path="StatusInfo.IsLogged"></Binding>
</MultiBinding>
</Image.Source>
</Image>
I create converter, which convert BitmapImage to gray scale if property IsLogged is false.
It look like this:
public class AvatarConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var image = values[0] as BitmapImage;
string s = values[1].ToString();
bool isLogged = System.Convert.ToBoolean(s);
if (!isLogged)
{
try
{
if (image != null)
{
var grayBitmapSource = new FormatConvertedBitmap();
grayBitmapSource.BeginInit();
grayBitmapSource.Source = image;
grayBitmapSource.DestinationFormat = PixelFormats.Gray32Float;
grayBitmapSource.EndInit();
return grayBitmapSource;
}
return null;
}
catch (Exception ex)
{
throw ex;
}
}
return image;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
It works good if only I bind on image source property type fo BitmapImage, but I need bind property type of Uri.
I have a fear of the creation variable BitmapImage in converter and as source use Uri.
An return this variable as Source of image. I think this is not ideal way. Maybe I am wrong.
What is your opinion
Some elegant solution?
Although you can do it with a converter, there is a much better option: using a shader effect. You'll find an implementation of a GreyscaleEffect on this page.
<Style x:Key="grayedIfNotLogged" TargetType="Image">
<Style.Triggers>
<DataTrigger Binding="{Binding StatusInfo.IsLogged}" Value="False">
<Setter Property="Effect">
<Setter.Value>
<fx:GrayscaleEffect />
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
...
<Image Source="..." Style="{StaticResource grayedIfNotLogged}" />

How to modify Image Source in WPF XAML dynamically

I have a WPF App that has (so far) 2 modes of display, regularmode and widgetmode.
I am using Prism 6 with MVVM design pattern.
MainWindowViewModel knows the mode of display.
ToolBarView has, as expected, a toolbar of buttons and the buttons shall be dynamically changed to different images depending on the mode of the view. If the mode is WidgetMode, it switches to the image with an identical name but with an '_w' added. So instead of "image.png", it's "image_w.png".
What I'd like to do is create a string in ToolBarView that is updated to either String.Empty or to "_w", depending on the mode. I'd also like the image root folder to be a global string, rather than a hardcoded string, so I have defined that in app.xaml.
<Application.Resources>
<sys:String x:Key="ImageURIRoot">/MyApp;component/media/images/</sys:String>
</Application.Resources>
Then in my toolbarview (a usercontrol), I did this:
<UserControl.Resources>
<converters:StringToSourceConverter x:Key="strToSrcConvert"/>
<sys:String x:Key="BtnImgSuffix">_w</sys:String>
.
.
.
</UserControl.Resources>
Note that the string is hardcoded; eventually, I will change it dynamically based off the windowmode.
I then put the Buttons in a Listbox
<ListBoxItem Style="{StaticResource MainButton_Container}">
<Button Command="{Binding ButtonActionDelegateCommand}" Style="{StaticResource Main_Button}">
<Image Source="{Binding Source={StaticResource ImageURIRoot}, Converter={StaticResource strToSrcConvert}, ConverterParameter='{}{0}button.png'}" />
</Button>
</ListBoxItem>
Converter code:
public class StringToSourceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (parameter is string)
{
return string.Format(parameter.ToString(), value);
}
return null;
}
public object ConvertBack(object value, Type targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
So that works. But what I want is to have the ConverterParameter equal "{}{0}button{1}.png", where {0} is the URI Root and {1} is the suffix. But I can't figure out how to do it. I know it's simple, but I can't put my finger on it!
Please help!
Figured it out and it was through multibinding. The way I did it was create a converter that inherits from IMultiValueConverter. Its "Convert" method looks like this:
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
ImageSourceConverter conv = new ImageSourceConverter();
int suffixPos = ((String)parameter).Length - 4;
var returnValue = ((String)parameter).Insert(suffixPos, values[1].ToString());
returnValue = Path.Combine(values[0].ToString(), returnValue);
ImageSource imgsrc = conv.ConvertFromString(returnValue) as ImageSource;
return imgsrc;
}
The xaml looks like this:
<Image Height="30" Width="40" diag:PresentationTraceSources.TraceLevel="High">
<Image.Source>
<MultiBinding Converter="{StaticResource stringsToSrcConvert}" ConverterParameter="buttonImg.png">
<Binding Source="{StaticResource ImageURIRoot}"/>
<Binding Source="{StaticResource BtnImgSuffix}"/>
</MultiBinding>
</Image.Source>
</Image>
Also, had to modify the URIRoot
<Application.Resources>
<sys:String x:Key="ImageURIRoot">pack://application:,,,/MyApp;component/media/images/</sys:String>
</Application.Resources>
Thanks, Clemens!

Windows Phone update/refresh binding

I have a textblock in my listbox called "feedTitle" which I want to change the forground color of. I use Foreground="{Binding Converter={StaticResource NewsTextColorConverter}}" for the binding of the forground color. Now the strange problem is that, if I choose a color in the listpicker("Lys" or "Dark" value) it runs the IValueConverter Convert method, but it dont show the color in the GUI, only if I restart my whole app it shows the color I chosen. It's like it only set the color of the forground of the textblock once.
MainPage.xaml
<ListBox Grid.Row="1" Name="feedListBox" ScrollViewer.VerticalScrollBarVisibility="Auto" SelectionChanged="feedListBox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel VerticalAlignment="Top">
<TextBlock TextDecorations="Underline" FontSize="24" Name="feedTitle" TextWrapping="Wrap" Margin="12,0,0,0" Foreground="{Binding Converter={StaticResource NewsTextColorConverter}}" Text="{Binding Title.Text, Converter={StaticResource RssTextTrimmer}}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And in my app file:
App.xaml
<Application.Resources>
<converter:NewsTextColorConverter xmlns:converter="clr-namespace:NordjyskeRss" x:Key="NewsTextColorConverter" />
</Application.Resources>
I use a listpicker where a user select the value "Mørk" or "Lys" and then I want the textblock forground color to update its forground color. I call the Convert method and pass null as arguments, it seems to run the method fine:
MainPage.cs
private void lpkThemes_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// Make sure we don't handle the event during initiation.
if (e.RemovedItems != null && e.RemovedItems.Count > 0)
{
if (this.lpkThemes.SelectedItem != null)
{
settings[THEMES_SETTING_KEY] = lpkThemes.SelectedItem.ToString();
if (lpkThemes.SelectedItem.ToString() == "Mørk")
{
n.Convert(null, null, null, null);
}
else
{
n.Convert(null, null, null, null);
}
}
}
}
This is where I use a IValueConverter to check for what color to use on the textblock and then add it:
MainPage.cs
public class NewsTextColorConverter : IValueConverter
{
protected IsolatedStorageSettings settings = IsolatedStorageSettings.ApplicationSettings;
protected const string THEMES_SETTING_KEY = "Themes";
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (settings.Contains(THEMES_SETTING_KEY))
{
string themesValue = (string)settings[THEMES_SETTING_KEY];
if (themesValue == "Mørk")
{
return new SolidColorBrush(Colors.Green);
}
else
{
return new SolidColorBrush(Colors.Blue);
}
}
return new SolidColorBrush(Colors.Green);
//throw new NotSupportedException("ColorToBurshConverter only supports converting from Color and String");
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
I think you need to redesign your app in the following way:
Add the following line into your app.xaml or page resources: <SolidColorBrush x:Key="brushListItemsForeground" Color="#FFFFFFFF" />
Replace Foreground="{Binding Converter={StaticResource NewsTextColorConverter}}" with Foreground="{StaticResource brushListItemsForeground}"
In your SelectionChanged:
var brush = (SolidColorBrush)Application.Current.Resources["brushListItemsForeground"]; if you’ve added the brush to app.xaml, or = (SolidColorBrush)this.Resources["brushListItemsForeground"]; if you’ve added the brush to page resources. Then change the Color property of the brush based on your settings.
P.S. There’re also other correct ways: e.g. create a SettingsContainer class that implements INotifyPropertyChanged, add it into some resource dictionary <local:SettingsContainer x:Key="mySettings" />, then bind to its properties e.g. Foreground="{Binding listItemsForeground, Source={StaticResource mySettings}}", when you need to change the value, change the listItemsForeground property of your class and raise PropertyChanged.
Currently, you’re abusing value converter using then as value providers, they were not designed for that, and that is why you have issues updating those values.

Setting Image in ListView based on criteria

I'm doing a budgeting module.
I will like to ask that how to set the GridViewColumn to display out my desired image based on the value which I retrieve from database which are "income" & "expenses". I know how to retrieve the value from the database & display in the lisview but my question for today is that I will like to have some condition which is when found "income" will populate with income image then found expense will populate with another image???
Can this be possible. Hope to receive reply as soon as possible. Thank you.
I will provide my codes for better refer:
XAML file:
<DataTemplate x:Key="CategoriesType">
<Border BorderBrush="#FF000000" BorderThickness="1,1,0,1" Margin="-6,-2,-6,-2">
<StackPanel Margin="6,2,6,2">
<TextBlock Text="{Binding Path=CategoriesType}"/>
</StackPanel>
</Border>
</DataTemplate>
<Style x:Key="MyItemContainerStyle" TargetType="{x:Type ListViewItem}">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Stretch" />
<!--<EventSetter Event="PreviewMouseLeftButtonDown" Handler="ListViewItem_PreviewMouseLeftButtonDown" />-->
</Style>
</Window.Resources>
<ListView Height="320" HorizontalAlignment="Left" Margin="12,154,0,0" Name="CategoriesListView" VerticalAlignment="Top" Width="316" ItemsSource="{Binding}" ItemContainerStyle="{DynamicResource MyItemContainerStyle}">
<ListView.View>
<GridView>
<GridViewColumn Header="Types" Width="40" CellTemplate="{DynamicResource CategoriesType}"/>
</GridView>
</ListView.View>
</ListView>
Add an image tag in the tamplate and use a converter to return the correct image based on the string value (untested code)
xaml:
<UserControl.Resources>
<Converters:TypeToImageConverter x:Key="typeToImageConverter" />
</UserControl.Resources>
<StackPanel Margin="6,2,6,2">
<Image Source="{Binding Path=CategoriesType,Converter={StaticResource typeToImageConverter}"/>
<TextBlock Text="{Binding Path=CategoriesType}"/>
</StackPanel>
TypeToImageConverter.cs:
public class TypeToImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null)
throw ...
var str = value.ToString();
if (str == "income")
return new BitmapImage(...);
if (str = "expenses")
return new BitmapImage(...);
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
I found the solution for my answer which is the format of the string. Which causing me to unavailable to retrieve the image with few hours of troubleshooting & debugging. I finally found out the solution :)
I solve it by like this:In my converter.cs
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
string str = (string)value;
string newString = str.TrimEnd();//Newly added compared with the old version
if (newString == "income")
return new BitmapImage(new Uri("pack://application:,,,/images/add.png"));
if (newString == "Expenses")
{
//return new BitmapImage(new Uri(#"pack://application:,,,/HouseWivesSavior;component/images/add.png"));
return new BitmapImage(new Uri("pack://application:,,,/images/edit.png"));
}
return null;
}
From referring the above that you can see that I added this code: "string newString = str.TrimEnd();"
Is because I don't want extra white space at the end of the string. As during insert into the database that my code is look like this:
if (IncomeButton.IsChecked == true) {
CategoryType = IncomeButton.Content.ToString();
}else{
CategoryType = ExpensesButton.Content.ToString();
}
During runtime, I found out that why the value look strange in the format of "Expenses " instead of "Expenses"... Therefore I tried with trim of the end part see how & Bingo. I got it working like a charm.
I refered this video to out that how to trace the value: http://www.youtube.com/watch?v=evO3_xutDYI
Thank you all guys for answering my question & sorry for wasting your time & effort to solve my question :) Good luck to all of you & have a nice day.

C# / WPF - DataGrid - Update Element Color after timeout

I currently use the following method to set the colour of my Row Background.
XAML
<Style TargetType="{x:Type xcdg:DataRow}">
<Setter Property="Background">
<Setter.Value>
<MultiBinding Converter="{StaticResource colorConverter}">
<Binding RelativeSource="{RelativeSource Self}" Path="IsSelected"/>
<Binding BindsDirectlyToSource="True" />
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
C#
public class ColourConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var isRowSelected = (bool)values[0];
var myInstance = (MyClass) values[1];
// Return default
if (isRowSelected || myInstance == null)
return DependencyProperty.UnsetValue;
// Get the check for the current field
return GetColour(myInstance) ?? DependencyProperty.UnsetValue;
}
private static SolidColorBrush GetColour(MyClass myInstance)
{
if (heartbeat == null)
{
return null;
}
// Is it more two minutes old?
return (myInstance.CreatedDateTime + TimeSpan.FromMinutes(2) < Clock.UtcNow())
? Brushes.LightPink
: Brushes.LightGreen;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException(this.GetType().Name + " cannot convert back");
}
}
The problem is that this Converter is only called on population of the DataRow with new values. What I really need is some sort of callback to change the colour after a certain time or to have the Converter reevaluated periodically.
The colour update doesn't have to be instant, just within a few seconds. If I have a callback for each row then I'd need as many threads to match (They are created and hence expire (which changes their colour) at different times), with ~1000 rows this doesn't seem like an efficient option.
The other option is to poll the rows on one thread periodically and reevaluate the converter on each iteration (Every 5 seconds?). I think this is likely the way to go but I don't know how to go about it in WPF.
Perhaps there's another approach or built in support for such a task?
Thanks in advance!
Should be possible to get the BindingExpression from the DataRow and simply call UpdateSource/UpdateTarget manually as many times as you need...
BindingExpression binding = Control.GetBindingExpression(DataRow.BackgroundProperty)
binding.UpdateSource;
Don't forget to also change the UpdateSourceTrigger property on the binding.

Categories

Resources