Refreshing a binding that uses a value converter - wpf

I have a WPF UI that is bound to an object. I'm using a ValueConverter to convert a property to a specific image by a business rule:
public class ProposalStateImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var proposal = value as Proposal;
var basePath = "pack://application:,,,/ePub.Content;component/Images/General/Flag_{0}.png";
string imagePath;
if(proposal.Invoice != null)
{
imagePath = string.Format(basePath, "Good");
}
else
{
imagePath = string.Format(basePath, "Warning");
}
var uri = new Uri(imagePath);
var src = uri.GetImageSource(); //Extention method
return src;
}
}
The element is a TreeView where the image is on the 2nd level:
<TreeView x:Name="tree"
ItemsSource="{Binding People}"
SelectedItemChanged="OnTreeItemChanged">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type dmn:Person}"
ItemsSource="{Binding Proposals}">
<StackPanel Orientation="Horizontal" ToolTip="{Binding Path=Fullname}" Margin="3">
<Image Margin="5,0,5,0" Width="16" Height="16" Source="pack://application:,,,/ePub.Content;component/Images/General/Person_Active.png" />
<TextBlock Text="{Binding Path=Firstname}" />
<TextBlock Text="{Binding Path=Lastname}" Margin="5,0,0,0" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type dmn:Proposal}">
<StackPanel Orientation="Horizontal" Margin="3">
<Image x:Name="invoiceImage" Width="16" Height="16" Margin="5,0,5,0" Source="{Binding, Converter={StaticResource ProposalStateImageConverter}, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Text="{Binding DeliveryDate, Converter={StaticResource textCulturedDateConverter}}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
It is working fine, but later, when the object's state changes, I want to refresh the image and make the value converter reevaluate. How is this possible?

It looks like you're only using a single value inside the converter and you're just doing a simple switch between two values so you could instead just do this directly in XAML with a trigger. This method also switches to a Binding against the Invoice property so that any change notifications for that property will cause the Trigger to update.
<HierarchicalDataTemplate >
<StackPanel Orientation="Horizontal" Margin="3">
<Image x:Name="invoiceImage" Width="16" Height="16" Margin="5,0,5,0" Source="good.png"/>
<TextBlock ... />
</StackPanel>
<HierarchicalDataTemplate.Triggers>
<DataTrigger Binding="{Binding Invoice}" Value="{x:Null}">
<Setter TargetName="invoiceImage" Property="Source" Value="warning.png"/>
</DataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>

Assuming you can't use INotifyPropertyChanged because you're binding to the whole object, you need to call BindingExpression.UpdateTarget.
The slight subtlety is in getting hold of the binding expression. This requires you to have a fairly intimate knowledge of the view: as far as I know, the only way to do this is to call BindingOperations.GetBindingExpression, passing the control and property whose binding you want to update, e.g.:
BindingOperations.GetBindingExpression(myImage, Image.SourceProperty).UpdateTarget();

Related

How to check the property value of the singleton(or static) class in the XAML?

There may be expressions that sound rude because I'm not a native English speaker.
I hope you to understand.
I am creating an application supporting the theme.
I would like to change the image in the XAML whenever the theme changes.
Currently, my requirements were implemented by using both cs code and XAML code as below.
<HierarchicalDataTemplate DataType="{x:Type models:SolutionStruct}" ItemsSource="{Binding Projects}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Converter={converters:ToImageByThemeConverter}, ConverterParameter='Solution'}" Width="16" Height="16" Margin="0 0 5 0">
</Image>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</HierarchicalDataTemplate>
The below code is the cs code to convert.
public class ToImageByThemeConverter : MarkupExtension, IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string result = string.Empty;
if (Theme.ThemeKind == ThemeKind.Dark)
{
if (parameter.ToString() == "Solution") result = "/Resources/solution.png";
else if (parameter.ToString() == "Project") result = "/Resources/project.png";
}
return result;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
public override object ProvideValue(IServiceProvider serviceProvider) => this;
}
The above code works well but I would like to implement the equal functionality by using only XAML code.
I think If I can check the property value of the Theme class then I can solve it.
Here are some of the vague codes that I hit upon to solve this problem. (not operate)
<HierarchicalDataTemplate DataType="{x:Type models:SolutionStruct}" ItemsSource="{Binding Projects}">
<StackPanel Orientation="Horizontal">
<Image Width="16" Height="16" Margin="0 0 5 0">
<Image.Triggers>
<Trigger Binding Source="{Binding {x:Static Theme}, Path="{ThemeKind}"} Value="Dark">
<Setter Source="/Resources/solution_dark.png"/>
</Trigger>
</Triggers>
</Image>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</HierarchicalDataTemplate>
Of course, the above code does not operate because I design vaguely.
Is it possible to design a code that operates with a similar feel to the above code?
or if you have another way to solve this problem please let me know, I don't obsess my way.
Thank you for reading.
You can bind to static properties by enclosing the property path in parentheses.
Adding the corresponding DataTrigger to the DataTemplate the HierarchicalDataTemplate would become:
<HierarchicalDataTemplate DataType="{x:Type models:SolutionStruct}"
ItemsSource="{Binding Projects}">
<StackPanel Orientation="Horizontal">
<Image x:Name="Image"
Source="/Resources/solution.png" />
<TextBlock Text="{Binding Name}" />
</StackPanel>
<HierarchicalDataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=(Theme.ThemeKind)}" Value="{x:Static ThemeKind.Dark}">
<Setter TargetName="Image" Property="Source" Value="/Resources/solution_dark.png" />
</DataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>

WPF Radcalender customization day template format

I want to cretae wpf radcalender in my wpf mvvm application. I can't able to customize the calender day template. I want to this format.
I am using this code. But the text has been overwrite the date.
xmal:
<telerik:RadCalendar Margin="30" SelectableDateStart="2015-01-01" SelectableDateEnd="2015-01-31" >
<telerik:RadCalendar.DayTemplate>
<DataTemplate>
<StackPanel>
<TextBox Text="Test"/>
</StackPanel>
</DataTemplate>
</telerik:RadCalendar.DayTemplate>
</telerik:RadCalendar>
I've worked with the RadCalendar within a Winrt app and here how i fixed that (with some help from the official Docs)
First add a new converter that will return the Date Label like that:
public class CellModelConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var cellModel = value as CalendarCellModel;
return cellModel.Label;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
then in you Xaml add a textBlock to display the Cell Date using the previous converter :
<telerik:RadCalendar Margin="30" SelectableDateStart="2015-01-01" SelectableDateEnd="2015-01-31" >
<telerik:RadCalendar.DayTemplate>
<DataTemplate>
<StackPanel>
<TextBox Text="Test"/>
<TextBlock Text="{Binding Converter={StaticResource CellModelConverter}}" FontSize="13.333" VerticalAlignment="Bottom" Margin="6,0,0,4" />
</StackPanel>
</DataTemplate>
</telerik:RadCalendar.DayTemplate>
</telerik:RadCalendar>
and don't forget to add the Converter to the Static Resources:
<converters:CellModelConverter x:Key="CellModelConverter" />
--EDIT 1
the code above work fine in a Winrt application, for a WPF project you don't need a converter at all :
<telerik:RadCalendar Margin="30" SelectableDateStart="2015-01-01" SelectableDateEnd="2015-01-31" >
<telerik:RadCalendar.DayTemplate>
<DataTemplate>
<StackPanel>
<TextBox Text="Test"/>
<TextBlock Text="{Binding}" FontSize="13.333" VerticalAlignment="Bottom" Margin="6,0,0,4" />
</StackPanel>
</DataTemplate>
</telerik:RadCalendar.DayTemplate>
</telerik:RadCalendar>
--EDIT 2
the above code shows the "test" text in all the cells including WeekName, DayName ..., to fix that you can use either a Converter or a DataTrigger, here how to do it using a DataTrigger :
<telerik:RadCalendar Margin="30" SelectableDateStart="2015-01-01" SelectableDateEnd="2015-01-31" >
<telerik:RadCalendar.DayTemplate>
<DataTemplate>
<StackPanel>
<TextBox Text="Test">
<TextBox.Style>
<Style TargetType="TextBox">
<Setter Property="Visibility" Value="Collapsed"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding DataContext.ButtonType,ElementName=tb}"
Value="Date">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<TextBlock x:Name="tb" Text="{Binding Converter={StaticResource CellModelConverter}}" FontSize="13.333" VerticalAlignment="Bottom" Margin="6,0,0,4" />
</StackPanel>
</DataTemplate>
</telerik:RadCalendar.DayTemplate>
</telerik:RadCalendar>
Output:

Bind a formula to a TextBox

I'm doing a little program that needs to calculate the thickness of a book using its page count.
Please note that I'm almost completely new to programing. And this is my first time on ths website.
Here's the XAML.
<StackPanel VerticalAlignment="Center" Margin="25 5 0 10">
<Slider Maximum="800" TickPlacement="BottomRight" TickFrequency="1"
IsSnapToTickEnabled="True" DockPanel.Dock="Right" x:Name="slNumPages"
Margin="0" LargeChange="16" SmallChange="2" Value="200" Minimum="16"
BorderThickness="0" />
<WrapPanel>
<TextBox Text="{Binding ElementName=slNumPages, Path=Value,
UpdateSourceTrigger=PropertyChanged}" Width="40" />
<TextBlock Text="Approx. thickness: " />
<TextBlock x:Name="tbApproxThickness" Text="..."/>
</WrapPanel>
How can I bind the TextBlock tbApproxThickness to a formula where the value of slider slNumPages gets multiplied by constant 0.0252?
here you go
<StackPanel VerticalAlignment="Center"
Margin="25 5 0 10"
xmlns:l="clr-namespace:CSharpWPF">
<StackPanel.Resources>
<l:ApproxThicknessConverter x:Key="ApproxThicknessConverter" />
</StackPanel.Resources>
<Slider Maximum="800"
TickPlacement="BottomRight"
TickFrequency="1"
IsSnapToTickEnabled="True"
DockPanel.Dock="Right"
x:Name="slNumPages"
Margin="0"
LargeChange="16"
SmallChange="2"
Value="200"
Minimum="16"
BorderThickness="0" />
<WrapPanel>
<TextBox Text="{Binding ElementName=slNumPages, Path=Value,
UpdateSourceTrigger=PropertyChanged}"
Width="40" />
<TextBlock Text="Approx. thickness: " />
<TextBlock x:Name="tbApproxThickness"
Text="{Binding Value,ElementName=slNumPages,Converter={StaticResource ApproxThicknessConverter}}" />
</WrapPanel>
</StackPanel>
following are the changed for the same
<l:ApproxThicknessConverter x:Key="ApproxThicknessConverter" />
Text="{Binding Value,ElementName=slNumPages,Converter={StaticResource ApproxThicknessConverter}}"
converter class
namespace CSharpWPF
{
class ApproxThicknessConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (double)value * 0.0252;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
You may need to refer to the Data Binding Overview page on MSDN to understand this answer. One simple solution would be to data bind a double property to the Slider.Value property and then reference this property in a formula property that does the calculation. Try something like this:
public double SliderValue
{
get { return sliderValue; }
set
{
sliderValue = value;
NotifyPropertyChanged("SliderValue"); // Implement INotifyPropertyChanged
FormulaValue = sliderValue * 0.0252;
}
}
public double FormulaValue { get; set; } // Implement INotifyPropertyChanged
...
<Slider Maximum="800" TickPlacement="BottomRight" TickFrequency="1"
IsSnapToTickEnabled="True" DockPanel.Dock="Right" x:Name="slNumPages"
Margin="0" LargeChange="16" SmallChange="2" Value="{Binding SliderValue}"
Minimum="16" BorderThickness="0" />
<WrapPanel>
<TextBox Text="{Binding ElementName=slNumPages, Path=Value,
UpdateSourceTrigger=PropertyChanged}" Width="40" />
<TextBlock Text="Approx. thickness: " />
<TextBlock x:Name="tbApproxThickness" Text="{Binding FormulaValue}" />
</WrapPanel>
Please bear in mind that you will need to implement the INotifyPropertyChanged Interface on these properties to make this work. You will also need to set the DataContext of the Window properly. Having done that, each time the Slider is moved, the property will automatically update the FormulaValue.

Prevent empty tooltips at a wpf datagrid

I am working on a calendar program, which consists mainly of a WPF DataGrid. As there is not always enough space to display all the entries of a day (which is a DataGridCell), a tooltip with all the entries of the day shell appear at mouse over. This works so far with the code snippet shown below. And now the (little) problem: If there are no entries for a day, no tooltip shell pop up. With the code below an empty tooltip pops up.
<DataGridTemplateColumn x:Name="Entry"
IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding EntryText}"
Foreground="{Binding EntryForeground}"
FontWeight="{Binding EntryFontWeight}">
</TextBlock>
<TextBlock Text="{Binding RightAlignedText}"
Foreground="Gray"
Background="Transparent">
<TextBlock.ToolTip>
<TextBlock Text="{Binding AllEntriesText}"/>
</TextBlock.ToolTip>
</TextBlock>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
The Databinding is made via
myCalDataGrid.Itemssource = _listOfDays;
in code behind, where a 'Day' is the view model for a single calendar row.
As H.B. suggested bind directly to the ToolTip property instead of using TextBlock and in case AllEntriesText is empty string you can apply a trigger on your TextBlock to disable your tooltip by setting the property ToolTipService.IsEnabled like this -
<TextBlock Text="{Binding RightAlignedText}"
Foreground="Gray"
Background="Transparent"
ToolTip="{Binding AllEntriesText}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="ToolTip"
Value="{x:Static system:String.Empty}">
<Setter Property="ToolTipService.IsEnabled" Value="False" />
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
Make sure to add namespace system in your xaml -
xmlns:system="clr-namespace:System;assembly=mscorlib"
Bind directly to the ToolTip property (do not create a TextBlock for it) and set AllEntriesText to null if there are no entries, then the ToolTip itself is also null and should not show.
Thanks for the solutions, they may work, no question. But I need a TextBlock for the tooltip to format and align the text. So I found this solution:
<TextBlock Text="{Binding RightAlignedText}"
HorizontalAlignment="Stretch"
TextAlignment="Right" Padding="2,0"
Foreground="Gray"
Background="Transparent"
ToolTipService.ShowDuration="60000"
ToolTipService.BetweenShowDelay="0"
ToolTipService.InitialShowDelay="0"
>
<TextBlock.ToolTip>
<ToolTip Visibility="{Binding EntryToolTipVisibility}">
<TextBlock Text="{Binding ToolTipText}"
TextAlignment="Left"
FontFamily="Courier New"/>
</ToolTip>
</TextBlock.ToolTip>
</TextBlock>
I bound the propertie "Visibility" of tooltip to a propertie "EntryToolTipVisibility" (of type Visibility) in the view model. See code snippet below.
public Visibility EntryToolTipVisibility
{
get
{
return _entries.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
}
}
Another option is to use an own Converter.
I prefer this way for example for a TextBlock tooltip that displays TextBlock's text, but for the case there is no text, the empty tooltip is not desired.
XAML code:
//step #1
xmlns:local="clr-namespace:MyNamespace"
//step #2 - into Window.Resources or other
<local:StringToVisibleTooltip x:Key="StringToVis" />
//step #3 - example of use
<TextBlock ...other attributes... TextTrimming="CharacterEllipsis">
<TextBlock.ToolTip>
<ToolTip Visibility="{Binding Path=Text, Converter={StaticResource StringToVis}}">
<TextBlock Text="{Binding Text}"/>
</ToolTip>
</TextBlock.ToolTip>
</TextBlock>
And code behind
namespace MyNamespace
{
public class StringToVisibleTooltip : IValueConverter
{
public StringToVisibleTooltip() { }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null && ((string)value).Length > 0) //empty string does not need tooltip
return Visibility.Visible;
else
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
}

Binding a Silverlight TabControl to a complex object using a converter

I am trying to bind my data to a tab control. I have got the headers displaying fine but I'm not sure how I get the content of the tabs to bind correctly based on my item template shown below.
I think I'm missing something when I'm creating the tab item but I'm not sure how to bind my MyCustomObject to each of the TabItem's.
XAML:
<sdk:TabControl ItemsSource="{Binding Singles,Converter={StaticResource TabConverter}}">
<sdk:TabControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</sdk:TabControl.ItemsPanel>
<sdk:TabControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBox Text="{Binding Converter={StaticResource RoundNumberConverter}}" Margin="2" />
<ListBox x:Name="Matches" ItemsSource="{Binding}" Margin="2">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding Converter={StaticResource SeedingConverter}, ConverterParameter=true}" Margin="2" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="{Binding Converter={StaticResource SeedingConverter}, ConverterParameter=false}" Margin="2" />
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Path=Player1Name}" Margin="2" />
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Path=Player2Name}" Margin="2" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</sdk:TabControl.ItemTemplate>
</sdk:TabControl>
Converter:
public class TabConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
IEnumerable<IGrouping<string, MyCustomObject>> source = value as IEnumerable<IGrouping<string, MyCustomObject>>;
if (source != null)
{
var controlTemplate = (ControlTemplate)parameter;
List<TabItem> result = new List<TabItem>();
foreach (IGrouping<string, MyCustomObject> tab in source)
{
result.Add(new TabItem()
{
Header = tab.Key,
DataContext = tab //not sure this is right?
});
}
return result;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
You're most of the way there, the following works for me:
Strip out your DataTemplate and drop its contents straight into a new UserControl, for the example here, lets call it MatchesView.
Then in your TabConverter, amend the contents of the foreach loop to something like the following:
TabItem tabitem = new TabItem();
tabitem.Header = tab.Key;
MatchesView tabview = new MatchesView();
tabview.DataContext = parameter;
tabitem.Content = tabview;
result.Add(tabitem);
Note: this requires that you pass your ViewModel to the TabConverter as a parameter, eg:
<sdk:TabControl SelectedItem="{Binding YourSelectedObject}" ItemsSource="{Binding YourCollectionObject, Converter={StaticResource TabConverter}, ConverterParameter={StaticResource YourViewModel}, Mode=TwoWay}" />
Then, as you have your ViewModel in each instance of the new control, adjust your binding accordingly!
Note that the trick is that you have a separate binding for the single instance of the Selected object
checking your problem I think there is no support for that. The WPF's TabItem contains ItemTemplate & ContentTemplate. The first is the template for the Header, the second is the template for the "body". In silverlight we still have no ContentTemplate. Haven't seen an official statement yet, but this guy says it won't be supported till SL5.

Resources