I'm creating weather app with forecast. I have created ListView with TextCell as entries.
I want to format test inside cell to XXX YY where:
XXX is value
YY is unit
I have observable collection declared in ContentPage and it is my ItemSource, I have another important property, weatherUnit.
private ObservableCollection<ForecastData> forecast = new ObservableCollection<ForecastData>();
private Unit weatherUnit { get; set; }
I'm creating Data template in constructor and setting everything up:
public WeatherFormsAppPage()
{
InitializeComponent();
var forecastWeatherDataTemplate = new DataTemplate(typeof(TextCell));
forecastWeatherDataTemplate.SetBinding(TextCell.TextProperty, "mainData.Temperature");
forecastWeatherDataTemplate.SetBinding(TextCell.DetailProperty, "date");
ForecastView.ItemsSource = forecast;
ForecastView.ItemTemplate = forecastWeatherDataTemplate;
}
How I can add to TextCell.TextProperty binding formatting to be temperature and weatherUnit. Temperature is double and weather unit have Extension that return String. Right now, only Temperature value is shown properly and date as detail:
You can create a readonly property that concats the values for you and then bind to that
public string WeatherData
{
get
{
return $"{Temperature} {Unit}";
}
}
binding
forecastWeatherDataTemplate.SetBinding(TextCell.TextProperty, "mainData.WeatherData ");
I also like David's approach. Having a get-only property in your JSON class is nothing to worry about. As you don't want to go that way, you can also write a converter class and add that to your binding.
public class StringToFormattedTempConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is string))
return value;
return $"{(string)value} \u00B0CC";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
And then add it to the binding like this.
forecastWeatherDataTemplate.SetBinding(TextCell.TextProperty, new Binding("mainData.Temperature", BindingMode.Default, new StringToFormattedTempConverter(), null));
Related
I like to bind to static properties whenever I can (e.g. when notification is not needed or when model anyway implement INotifyPropertyChanged for other purposes), e.g.:
Visibility="{Binding IsAdministractor, Source={x:Static local:User.Current}, Converter={local:FalseToCollapsedConverter}}"
The problem is that such evaluation works at design-time too, making it hard to work with designer.
Normal bindings doesn't work in design-time and I can utilize FallbackValue to specify design-time only values (I have never yet used FallbackValue in run-time).
Is there an easy way to make binding to static properties invalid (disable them) during design-time?
I can temporarily rename property, e.g. IsAdministrator123, but this is tedious.
You can check if you're in design mode either in the Converter or in the static Current or in the IsAdministractor(typo here?) property and just return whatever state you'd like to see.
EDIT:
Here's some code for a MarkupExtension (untested)
public class BindingWithDesignSupport : MarkupExtension
{
public BindingWithDesignSupport(){}
public BindingWithDesignSupport(BindingBase binding)
{
Binding = binding;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return DesignerProperties.GetIsInDesignMode(new DependencyObject()) ? DesignTimeValue : Binding.ProvideValue(serviceProvider);
}
public BindingBase Binding { get; set; }
public object DesignTimeValue { get; set; }
}
you should be able to use it like:
Visibility="{BindingWithDesignSupport {Binding IsAdministractor, Source={x:Static local:User.Current}, Converter={local:FalseToCollapsedConverter}},DesignTimeValue=Visibility.Visible}"
It's possible to attach converter to all such properties, which has FallbackValue (used in design-time) and Converter (to supply run-time converter) properties:
public class RuntimeConverter : MarkupExtension, IValueConverter
{
public object FallbackValue { get; set; }
public IValueConverter Converter { get; set; }
public RuntimeConverter() { }
public override object ProvideValue(IServiceProvider serviceProvider) => this;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (LicenseManager.UsageMode == LicenseUsageMode.Designtime)
return FallbackValue;
if (Converter == null)
return value;
return Converter.Convert(value, targetType, parameter, culture);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (LicenseManager.UsageMode == LicenseUsageMode.Designtime)
return FallbackValue;
if (Converter == null)
return value;
return Converter.ConvertBack(value, targetType, parameter, culture);
}
}
then in design-time it is possible to change value returned by static property:
<!-- split in multiple lines for readability -->
Visibility="{Binding IsPowerUser, Source={x:Static local:User.Logged},
Converter={local:RuntimeConverter Converter={local:FalseToCollapsedConverter},
FallbackValue=Collapsed}}">
You could use design time data to put the design time view model into the state you want to design against.
Or for simple properties you can initialise them with the desired design time value in the viewmodel e.g.
public bool IsAdministractor { get; set; } = true;
I have a TextBlock as follow:
<TextBlock Text="You don't have any more items." Visibility="{binding}"
and in code behind I defined a Stack called items as follow:
private Stack<Item> _items;
How do I bind the text visibility in xaml to visible when _item.Any is false?
There are several steps to achieving what you want to do and they are all described here
You need to create a value converter similar to this;
public class EmptyCollectionToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var collection = (Stack<int>) value;
return collection.Any() ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Then you need to add a reference to is in your resource dictionary in your xaml like this;
<views:EmptyCollectionToVisibilityConverter x:Key="EmptyCollectionToVisibilityConverter"/>
Finally bind your property in your view model to the visibility of your control and give the binding the converter like this;
Visibility="{Binding Items, Converter={StaticResource EmptyCollectionToVisibilityConverter}}"
Your property will probably need to be an observableCollection (which will mean changing the value converter example I gave you slightly.
I'd probably go with:
private Stack<Item> _items;
// bind to this property using converter
public bool IsVisible => !(_items?.Any(...) ?? false);
You shouldn't expose your _stack directly, but e.g. use methods to do something (because you need to rise notification every time you push/pop an item):
public void PushItem(Item item)
{
_items.Push(item);
OnPropertyChanged(nameof(IsVisible)); // implement INotifyPropertyChanged
}
I am working on a WPF app. In this app, I have some XAML segments. I need to display the XAML segments in a TextBlock. In my XAML, I have the following line:
<TextBlock Text="{Binding Path=XamlSegment, Converter={StaticResource XamlToTextConverter}}" />
The XamlSegment property will have a value like "-0.275*x2". In an attempt to render this XAML in my UI so that the Superscript shows, I'm using the XamlToTextConverter, which is defined as follows:
namespace MyApp.Converters
{
public class XamlToTextConverter : IValueConverter
{
private static readonly Regex Regex = new Regex("(<.*?)>(.*)(</.*?>)", RegexOptions.Compiled);
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// value looks like this: -0.275*x<Run Typography.Variants="Superscript">2</Run>
var xamlText = value as string;
if (xamlText != null)
{
try
{
xamlText = "<TextBlock>" + xamlText + "</TextBlock>";
var xamlTextWithNamespace = Regex.Replace(xamlText, "$1 xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\">$2$3");
return XamlReader.Parse(xamlTextWithNamespace);
}
catch (Exception)
{
return value;
}
}
else
{
return value;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
When this converter gets ran, my UI shows "System.Windows.Controls.TextBlock" instead of the rendered XAML. Yet, I don't know why. How do I get my XamlSegment to render in my UI?
Thanks
The Text property of the TextBlock will be set to a TextBlock object according to your XamlToTextConverter. Since the Text property should be type of string, it doesn't know how to show a TextBlock as string. So the default way to get thing done is to use the ToString method on TextBlock to fill the Text property, which makes the value of Text as "System.Windows.Controls.TextBlock".
It seems that you would like to dynamically render the xaml. You could reference this link(Loading XAML XML through runtime?) for a solution.
I have a C# WPF 4.51 application. On one of my XAML forms I have a list box that has its ItemsSource property bound to a property in my main ViewModel that is of type Collection. When the Collection was of type string, everything worked fine and I saw the collection of strings in the list box.
But then I changed the type in the Collection to a class named ObservableStringExt. The class has two fields: StrItem that contains the string I want displayed in the list box, and IsSelected, a supporting field. I then created a value converter to extract the StrItem field and return it.
However, when I look at the targetType passed to the Convert() method of the value converter I see a type of IEnumerable. Given that the Count property in that parameter matches the number of list items expected, it looks like the Convert() method is receiving a reference to the entire Collection instead of ObservableStringExt, the type of each item in the Collection. This of course is a problem. What is causing this? I have done this sort of thing many times in Windows Phone and WinRT (windows store apps) many times without trouble.
Here is the code for the value converter:
public class ObservableStringExtToStrItem : IValueConverter
{
// The targetType of the value received is of type IEnumerable, not ObservableStringExt.
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is ObservableStringExt)
return (value as ObservableStringExt).StrItem;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Below is the XAML code for the list box. Note Commands_FrequentyUsed is a property of type ObservableCollectionWithFile found in the main view model, which is the data context for the entire form:
<ListBox x:Name="listFrequentlyUsedCommands"
Width="278"
Height="236"
Margin="30,103,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
ItemsSource="{Binding Commands_FrequentyUsed.Collection,
Converter={StaticResource ObservableStringExtToStrItem}}" />
Here is the code for the class that contains the Collection that the list box binds to and the class the Collection contains:
public class ObservableStringExt
{
public string StrItem { get; set;}
public bool IsSelected{ get; set; }
}
public class ObservableCollectionWithFile : BaseNotifyPropertyChanged
{
public const string CollectionPropertyName = "Collection";
private ObservableCollection<ObservableStringExt> _observableCollection = new ObservableCollection<ObservableStringExt>();
public ObservableCollection<ObservableStringExt> Collection
{
get { return _observableCollection; }
private set { SetField(ref _observableCollection, value); }
}
} // public class ObservableCollectionWithFile
I just had the same problem. Not sure how it should normally work, but changing the converter to also convert list of items helped (I found this easier than creating a separate converter for List)
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var list = value as IEnumerable<ObservableStringExt>;
if (list != null)
{
return list.Select(x => Convert(x, typeof(string), null, culture));
}
if (value is ObservableStringExt)
return (value as ObservableStringExt).StrItem;
}
I have an IValueConverter that I've added a dependency property to because I need the viewmodel to set the property.
public class ParameterValueToString: ContentControl, IValueConverter
{
public object Convert(object value
, Type targetType
, object parameter
, CultureInfo culture)
{
return Converter.ToString(value); //Throws NullReferenceException
}
public object ConvertBack(object value
, Type targetType
, object parameter
, CultureInfo culture)
{
/* convert back */
}
internal const string ConvertPropertyName = "Converter";
public static readonly DependencyProperty ConverterProperty
= DependencyProperty.RegisterAttached(ConvertPropertyName
, typeof(IParameterConverter)
, typeof(ParameterValueToString));
public IParameterConverter Converter
{
get { return (IParameterConverter) GetValue(ConverterProperty); }
set { SetValue(ConverterProperty, value); }
}
}
The property is an object that does the conversion from ParameterValue to string. I'm trying to reuse code.
The VM has a property of IParameterConverter called ParameterConverter.
The WPF I've setup:
<Namespace:ParameterValueToString x:Key="pvts" Converter="{Binding ParameterConverter}"/>
But when I try to use the Converter property I get a NullReferenceException.
The end use for the converter is to convert a list of ParameterValue into a string (and turn that string back into a list). This is to make is easy for the user to enter the values: as a newline separated list.
How can I get the converter to use IParameterConverter?