I have a WPF app. In this app, I have a ComboBox. The ComboBox display a list of algabra formulas. I want my students to be able to choose a formula. The formulas include superscripts. For that reason, I think I need to use a TextBlock like this:
<TextBlock>
<Run>x</Run>
<Run Typography.Variants="Superscript">2</Run>
<Run>+ 2xy </Run>
</TextBlock>
I am putting those formulas in
public class Formula
{
public string Text { get; set; }
public Formula(string text)
{
this.Text = text;
}
}
public class MyViewModel
{
public MyViewModel()
{
this.Formulas = new List<Formula>
{
new Formula("<TextBlock><Run>x</Run><Run Typography.Variants=\"Superscript\">2</Run><Run>+ 2xy </Run></TextBlock>"),
new Formula("<TextBlock><Run>x</Run><Run Typography.Variants=\"Superscript\">3</Run><Run>+ 3xy </Run></TextBlock>")
};
}
}
I am then trying to display those formulas, formatted, as ComboBoxItems. Currently, I have the following:
<ComboBox ItemsSource="{Binding Path=Formulas}" DisplayMemberPath="Text" />
This approach does not show the formulas formatted. Is there a way to bind ComboBoxItems to show formatted values? If so, how?
Thanks!
I would suggest to look into libraries that offer proper display of formulas (a similar answer here)
Although if you want to make this approach work you can do it the following way.
<Window x:Class="BindFormulas.MainWindow"
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"
xmlns:local="clr-namespace:BindFormulas"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:XamlTextToObjectConverter x:Key="XamlTextToObjectConverter" />
</Window.Resources>
<StackPanel>
<ComboBox ItemsSource="{Binding Path=Formulas}">
<ComboBox.ItemTemplate>
<DataTemplate DataType="local:Formula">
<ContentControl Content="{Binding Text, Converter={StaticResource XamlTextToObjectConverter}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</Window>
The converter that will convert the XAML text to actual objects:
public class XamlTextToObjectConverter : IValueConverter
{
private static readonly Regex Regex = new Regex("(<.*?)>(.*)(</.*?>)", RegexOptions.Compiled);
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var xamlText = value as string;
if (xamlText != null)
{
var xamlTextWithNamespace = Regex.Replace(xamlText, "$1 xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\">$2$3");
try
{
return XamlReader.Parse(xamlTextWithNamespace);
}
catch (Exception) // catch proper exceptions here, not just Exception
{
return value;
}
}
else
{
return value;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Again, probably you'd be better off with a library that does this properly :)
This approach is wrong for multiple reasons:
The Formula class probably shouldn't know about things like TextBlock and Run. That's not a model class's concern.
Also I'm sure you can provide a XAML string that will trip this converter up.
That said, if this will be a very simple app, where you will be a 100% sure that the XAML strings can be properly converted, then maybe this approach is OK as well.
Related
I have the following class hierarchy:
namespace WpfBindingProblem
{
public class Top
{
public IList<Mid> MidList { get; } = new ObservableCollection<Mid>();
}
public class Mid
{
public IList<Bot> BotList { get; } = new ObservableCollection<Bot>();
}
public class Bot
{
}
}
And I have this XAML window:
<Window x:Class="WpfBindingProblem.MainWindow"
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"
xmlns:local="clr-namespace:WpfBindingProblem"
mc:Ignorable="d"
Title="MainWindow" Height="217.267" Width="333.686">
<Window.DataContext>
<local:Top/>
</Window.DataContext>
<Window.Resources>
<local:TriggersToString x:Key="TriggersToString"/>
</Window.Resources>
<Grid>
<ListView Margin="10" ItemsSource="{Binding MidList}" x:Name="ThatList">
<ListView.Resources>
<DataTemplate DataType="{x:Type local:Mid}">
<TextBlock Text="{Binding BotList, Converter={StaticResource TriggersToString}}" />
</DataTemplate>
</ListView.Resources>
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Add mid" Click="AddMid"/>
<MenuItem Header="Add bot to selected mid" Click="AddBot" />
</ContextMenu>
</ListView.ContextMenu>
<ListView.View>
<GridView>
<GridViewColumn/>
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
With these handlers:
namespace WpfBindingProblem
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void AddMid(object sender, RoutedEventArgs e)
{
if(DataContext is Top p)
{
p.MidList.Add(new Mid());
}
}
private void AddBot(object sender, RoutedEventArgs e)
{
if(ThatList.SelectedItem is Mid c)
{
c.BotList.Add(new Bot());
}
}
}
}
And this converter (as a stand-in for any arbitrary converter):
namespace WpfBindingProblem
{
[ValueConversion(typeof(IList<Bot>), typeof(string))]
public class TriggersToString : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if(value is IList<Bot> list)
{
return list.Count.ToString();
}
throw new InvalidOperationException();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new InvalidOperationException();
}
}
}
In the window that appears when we run this example, I can right click and choose "Add mid" so an instance of Mid is added to the Top data context, and the list view is updated accordingly, showing the number 0 (as per the conversion logic)
However, when I click "Add bot to selected mid", an instance of Bot is added to the selected Mid (I can verify this using breakpoints), but the list view is not updated accordingly (I expected 0 to be changed to 1, but the converter is not called again for that particular instance of Mid).
Why does this change not trigger an update of the GUI?
I know I can work around this with some hacks (like setting the data context to null and back, or possibly by invoking explicit updates using dependency properties), but there are two reasons why I'd like to avoid that:
My actual code is more complex than this MCVE and it would look very ugly.
I've already sprinkled all my (actual) classes with all the required the ObservableCollections and the INotifyPropertyChanged interfaces, precisely so that I wouldn't need to perform manual updates — so I feel like automatic updates should happen in this case, unless I've missed something.
Why does this change not trigger an update of the GUI?
Because the source property of the binding (BotList) is not updated. The converter is invoked only when the data bound property is updated.
You could use a MultiBinding as suggested by #Sinatr or you could
bind directly to the Count property of the collection:
<TextBlock Text="{Binding BotList.Count}" />
implement the INotifyPropertyChanged interface in the Mid class and raise the PropertyChanged event for the BotList property whenever an item is added to it. Handle CollectionChanged.
You might also move your convert logic to the view model, bind to a property of this one and also raise the PropertyChanged for it whenever you want the binding to be refreshed.
You can use multi binding:
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource TriggersToString}">
<Binding Path="BotList" />
<Binding Path="BotList.Count" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
and multi value converter:
public class TriggersToString : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) =>
(values[0] as IList<Bot>)?.Count.ToString(); // first binding
...
}
This way the converter is called whenever either of bindings is updated.
I am using xml binding with my wpf controls, the XMLDocument is an exposed property of ViewModel. Here is the code:
public class ViewModel : ViewModelBase
{
private XmlDocument _xmlDataProvider;
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
base.RaisePropertyChangedEvent("Name");
}
}
public XmlDocument XmlDataProvider
{
get { return _xmlDataProvider; }
set
{
_xmlDataProvider = value;
base.RaisePropertyChangedEvent("XmlDataProvider");
}
}
}
And my XAML code Looks like this:
<UserControl x:Name="ctrlTemplate" x:Class= "CtrlTemplate"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WPFControl.UI"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:xckt="clr-namespace:Xceed.Wpf.Toolkit;assembly=Xceed.Wpf.Toolkit"
mc:Ignorable="d"
DataContext="{DynamicResource ViewModel}">
<UserControl.Resources>
<local:ViewModel x:Key="ViewModel" />
</ResourceDictionary>
</UserControl.Resources>
The following control is binded to a node in my xml:
<DatePicker DataContext="{Binding Path=XmlDataProvider}" SelectedDate="{Binding XPath=dataDocument/loan/paymentDates/paymentDate[1], Converter={StaticResource NullToDateConverter}, UpdateSourceTrigger=PropertyChanged}"/>
My converter in the code segment is as follows:
public class NullToDateConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (String.IsNullOrEmpty(value.ToString()))
{
return DateTime.Now.Date;
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value.ToString();
}
}
The converter works as intended, if the value of the node is empty. It sets the value of the datetime control to current value.
But I am facing the issue, that if the user for any reason does not change the value of datepicker and tries to save the xml, the value of the node in the xml remains null. What is the best way to do so?
WPF won't update the source if it doesn't think that the target's value is different from the source's. I.e. if the target value doesn't change.
You can, however, force WPF to update the source by calling the BindingExpression.UpdateSource() method. Without a good, minimal, complete code example that reliably reproduces the problem, it's impossible to say specifically how you'd incorporate this into your code. One obvious option would be to call it when the XML is to be saved (i.e. just before).
That might look something like this:
BindingOperations
.GetBindingExpression(datePicker1, DatePicker.SelectedDateProperty)
.UpdateSource();
That assumes, of course, that you name your DatePicker control as datePicker1.
This will ensure that whatever the current value of SelectedDate, it is copied back to the original source for the binding (i.e. the path in your XML).
I've gotten to the point where I need to add localization to my WPF MVVM application (I use Caliburn.Micro + Autofac).
I did some research, and I've found many different ways to accomplish it, but none provide a solution to localize the text of a dialog.
As dialogs I use a DialogViewModel that Caption and Message string properties, and I show it in a DialogView using CM's WindowManager.
What I have atm is something like
this.windowManager.ShowDialog(new DialogViewModel("Hello!", "Hello everybody!!"))
but also things like
this.windowManager.ShowDialog(new DialogViewModel("Hello!", "Hello " + this.Name + "!!"))
I thought I could use a resource string like "Hello {0}!!" and use it this way
this.windowManager.ShowDialog(new DialogViewModel("Hello!", string.Format(languageResources.HelloName, this.Name)))
Is it good to do reference the localization resources from the ViewModel layer?
Resources is the data that uses a View, and my opinion is that is not advisable from the ViewModel refer to resources. On the other hand, if it is a class (may be static) that stores a specific strings, and knows nothing of the View it will be some abstraction that can be in the ViewModel. In any case, you should try to work with the resources on the side View using techniques that I will give, or any other.
Using x:Static Member
In WPF, it is possible to bind static data from a class like this:
<x:Static Member="prefix : typeName . staticMemberName" .../>
Below is an example where the format string is in a class, the format used to display the date and time.
XAML
xmlns:local="clr-namespace:YourNameSpace"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
<Grid>
<TextBlock Text="{Binding Source={x:Static sys:DateTime.Now}, StringFormat={x:Static Member=local:StringFormats.DateFormat}}"
HorizontalAlignment="Right" />
<TextBlock Text="{Binding Source={x:Static sys:DateTime.Now}, StringFormat={x:Static Member=local:StringFormats.Time}}" />
</Grid>
Code behind
public class StringFormats
{
public static string DateFormat = "Date: {0:dddd}";
public static string Time = "Time: {0:HH:mm}";
}
In this case, the StringFormats class be regarded as a resource, although actually it is a normal class. For more information, please see x:Static Markup Extension on MSDN.
Using Converter
If you have the resources stored in Application.Current.Resources and need to add some logic, in this case, you can use the converter. This example is taken from here:
XAML
<Button Content="{Binding ResourceKey, Converter={StaticResource resourceConverter}}" />
Code behind
public class StaticResourceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var resourceKey = (string)value;
// Here you can add logic
return Application.Current.Resources[resourceKey];
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new Exception("The method or operation is not implemented.");
}
}
Note: In the converter, it is better not to use heavy logic, because it can affect the performance. For more complex logic, see below.
Attached Behavior
Attached behavior should be used for complex actions with visual elements when no x:Static Member and converter is not helped. Attached behavior is very powerful and convenient solution that fully satisfies the MVVM pattern, which can also be used in the Blend (with a pre-defined interface). You can define an attached property in which property handler to access elements and to its resources.
Examples of implementation attached behaviors, see below:
Set focus to a usercontrol when it is made visible
Animated (Smooth) scrolling on ScrollViewer
Setting WindowStartupLocation from ResourceDictionary throws XamlParseException
Example with converter
App.xaml
Here I store strings for each culture.
<Application x:Class="MultiLangConverterHelp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
StartupUri="MainWindow.xaml">
<Application.Resources>
<sys:String x:Key="HelloStringEN">Hello in english!</sys:String>
<sys:String x:Key="HelloStringRU">Привет на русском!</sys:String>
</Application.Resources>
</Application>
MainWindow.xaml
The input is the current culture, which can be obtained within the converter, for simplicity of an example I did so.
<Window x:Class="MultiLangConverterHelp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MultiLangConverterHelp"
WindowStartupLocation="CenterScreen"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:StaticResourceConverter x:Key="converter" />
<local:TestViewModel x:Key="viewModel" />
</Window.Resources>
<Grid DataContext="{StaticResource viewModel}">
<TextBlock Text="{Binding Path=CurrentCulture, Converter={StaticResource converter}}" />
</Grid>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public class StaticResourceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var currentCulture = (string)value;
if (currentCulture.Equals("EN-en"))
{
return Application.Current.Resources["HelloStringEN"];
}
else if (currentCulture.Equals("RU-ru"))
{
return Application.Current.Resources["HelloStringRU"];
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return DependencyProperty.UnsetValue;
}
}
public class TestViewModel : NotificationObject
{
private string _currentCulture = "EN-en";
public string CurrentCulture
{
get
{
return _currentCulture;
}
set
{
_currentCulture = value;
NotifyPropertyChanged("CurrentCulture");
}
}
}
Also, I advise you to learn more simple ways, which is already in the WPF technology:
WPF Localization for Dummies
WPF Globalization and Localization Overview
I am currently trying to displaying images in my Windows 8 application. I have a method which populates a property of type List<string> with a number of paths to images. I wish to display these images on screen.
Thus, I have implemented a converter to go from string to image. However, I get the errors :
The name "StringToImageConverter" does not exist in the namespace
"using:TestApp.Converters".
'TestApp.Converters.StringToImageConverter' does not implement
interface member
'Windows.UI.Xaml.Data.IValueConverter.ConvertBack(object,
System.Type, object, string)'
'TestApp.Converters.StringToImageConverter' does not implement
interface member
'Windows.UI.Xaml.Data.IValueConverter.Convert(object, System.Type,
object, string)'
Here is the code from my Converter :
namespace TestApp.Converters
{
public sealed class StringToImageConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
try
{
return new BitmapImage(new Uri((string)value));
}
catch
{
return new BitmapImage();
}
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
And from my XAML file :
<common:LayoutAwarePage
...
xmlns:converters="using:TestApp.Converters"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Page.Resources>
<converters:StringToImageConverter x:Key="StringToImageConverter"> </converters:StringToImageConverter>
</Page.Resources>
...
<ItemsControl ItemsSource="{Binding Path=test}" Grid.Column="1" Grid.Row="3" Grid.ColumnSpan="4"
HorizontalContentAlignment="Stretch">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Image Source="{Binding Converter={StaticResource StringToImageConverter}}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
...
Should this work for displaying my images in the Windows 8 application? The List<string> of image paths is called test and is in the code behind of the xaml file.
Thanks very much for any and all help with this :)
Apparently there are two types of IValueConverters:
Windows.UI.Xaml.Data.IValueConverter
System.Windows.Data.IValueConverter
It sounds like your framework is expecting the former, while you're implementing the latter.
You probably also need to change this:
xmlns:converters="using:TestApp.Converters"
to this:
xmlns:converters="clr-namespace:TestApp.Converters"
Windows.UI.Xaml.Data.IValueConverter expects the last parameter to be a string, not a CultureInfo
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:System.Collections;assembly=mscorlib"
xmlns:p="clr-namespace:System;assembly=mscorlib"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:Entities="clr-namespace:Entities;assembly=Entities"
mc:Ignorable="d"
x:Name="XXXXX"
x:Class="AAAA.XXXXX" Title="Seciones" Height="644.305" Width="909.579"
xmlns:c="clr-namespace:AAAA">
<Window.Resources>
<c:StringToImageConverter x:Key="stringToImageConverter"/>
</Window.Resources>
.....
</Window>
I like design time data, especially when creating small widgets. For this very simple use case I'm having trouble binding to the properties of a design-time list which I have created in xaml.
Please find my ViewModel, View and SampleData below;
ViewModel
internal class SummaryViewModel : ViewModelBase
{
public string Title { get; set; }
public IList<Person> PersonList { get; set; }
internal SummaryViewModel()
{
PersonList = new List<Person>();
}
}
Sample Data
<ViewModel:SummaryViewModel xmlns:ViewModel="ViewModel" Title="Test Title">
<ViewModel:SummaryViewModel.Connections>
<ViewModel:ConnectionViewModel Id="0" />
<ViewModel:ConnectionViewModel Id="1" />
</ViewModel:SummaryViewModel.Connections>
</ViewModel:SummaryViewModel>
View
<StackPanel x:Class="View.SummaryView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="100"
d:DesignWidth="100"
d:DataContext="{d:DesignData Source=/DesignData/SampleSummaryViewModel.xaml}"
Orientation="Vertical"
Background="LightGreen">
<!-- This Works -->
<Label FontSize="10" FontWeight="Bold" Content="{Binding Title}" />
<!-- This Works -->
<ListBox ItemsSource="{Binding PersonList}" />
<!-- This DOESN'T work -->
<Label FontSize="8" Content="{Binding PersonList, Path=Count}"/>
</StackPanel>
How would you configure SampleData such that you could bind to the Count of a list specified therein?
I have tried setting the resource type as both DesignData and DesignDataWithDesignTimeCreatableTypes with no luck.
It should be:
<Label FontSize="8" Content="{Binding Path=PersonList.Count}"/>
Also Mårten is correct, you should use an ObservableCollection instead.
HTH
CityView, just as a side note: to debug DataBinding I usually use an empty converter which only returns the value it was given. I put a breakpoint in there and that way I can see what exactly is going back and forth.
public class BindTestConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
}
Combined with that and what the Output window tells me usually leads me to a solution to the problem at hand.
It should work, but become a one-time binding since your list does not implement INotifyPropertyChanged and therefore the binding is not updated when Count changes.
Try using an ObservableCollection<Person> instead.