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;
Related
I have a complex window with various controls that are visible or collapsed based on bool values. I want to add a custom attribute to show all of these controls during design time.
My implementation of the attribute looks like this:
public static class CustomAttributes
{
private static bool? _inDesignMode;
public static readonly DependencyProperty Visibility = DependencyProperty.RegisterAttached(
"Visibility",
typeof(Visibility),
typeof(CustomAttributes),
new PropertyMetadata(VisibilityChanged));
private static bool InDesignMode
{
get
{
if (!_inDesignMode.HasValue)
{
var prop = DesignerProperties.IsInDesignModeProperty;
_inDesignMode =
(bool)DependencyPropertyDescriptor.FromProperty(prop, typeof(FrameworkElement)).Metadata.DefaultValue;
}
return _inDesignMode.Value;
}
}
public static Visibility GetVisibility(DependencyObject dependencyObject)
{
return (Visibility)dependencyObject.GetValue(Visibility);
}
public static void SetVisibility(DependencyObject dependencyObject, Visibility value)
{
dependencyObject.SetValue(Visibility, value);
}
private static void VisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!InDesignMode)
return;
d.SetValue(Control.VisibilityProperty, e.NewValue);
}
}
In XAML I use it like this:
<Button Visibility="{Binding SomeBoolValue, Converter={StaticResource BoolToVisibility}}"
helper:CustomAttributes.Visibility="Visible"
/>
However, it does not seem to work. I use some other custom attributes like this and they do their job, but visibility doesn't trigger, it just stays collapsed in the design view. What am I missing?
Edit:
Thank you for pointing me to the right direction. The solution to my problem did not require the custom attribute as I first assumed it would. To achieve the design-time behavior that I wanted, I modified the converter implementation as suggested in the accepted answer below.
Think a little deeper about the logic you created.
The UI element does not have TWO Visibility properties, it is the only one.
But you want to manipulate this property in two ways at the same time: through the binding and the attached property.
Thus, you have created competition between them for this property.
And the property will take on the value that will be assigned to it last.
The attached property will be triggered only once when the Button is initialized (from the example).
And the binding will be triggered when the Data Context and/or its SomeBoolValue property changes.
But the Data Context of the Window is set later than the initialization of the UI elements of this Window.
I see several solutions.
The easiest one, if you need to ALWAYS show elements in Design Mode, is to add the appropriate logic to the converter.
In its simplest form, an example of such a converter:
/// <summary>Bool to Visibility converter.</summary>
[ValueConversion(typeof(bool), typeof(Visibility))]
public class BooleanToVisibilityConverter : IValueConverter
{
public static bool IsDesignMode { get; } = DesignerProperties.GetIsInDesignMode(new DependencyObject());
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool val)
return IsDesignMode || val
? Visibility.Visible
: Visibility.Collapsed;
return DependencyProperty.UnsetValue;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
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));
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 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 a custom method that returns a Brush that will be used as a background:
class ListBoxBackgroundSelector : BackgroundBrushSelector
{
public Brush fondo1 { get; set; }
public Brush fondo2 { get; set; }
private static bool usado = false;
public override Brush SelectBrushStyle(object item, DependencyObject container)
{
Brush fondo = null;
if (usado)
{
fondo = fondo1;
}
else
{
fondo = fondo2;
}
usado = !usado;
return fondo;
}
which extends from the abstract class
public abstract class BackgroundBrushSelector : ContentControl
{
public abstract Brush SelectBrushStyle(object item, DependencyObject container);
protected override void OnContentChanged(object oldContent, object newContent)
{
base.OnContentChanged(oldContent, newContent);
Background = SelectBrushStyle(newContent, this);
}
}
And I am using it in the following XAML declaration
<Grid.Background>
<local:ListBoxBackgroundSelector Background="{Binding}" fondo1="#19001F5B" fondo2="#FFFFFFFF" />
</Grid.Background>
But Visual Studio highlights the local:ListBoxBackgroundSelector line with an error that says
The specific value cannot be assigned. The expected value is "Brush"
But the method returns a Brush! And its return is being assigned to the Background, which indeed expects a Brush.
The project compiles and run, but when it reaches the page where is all this stuff, it just breaks with an "Unhandled Exception" and nothing else to look at.
There is something in the middle that I am not aware of. Can anyone help?
The method you wrote returns a brush, but you didn't place the method in the Background, you placed your class, which inherits from ContentControl. The way I see it, you now have a few options. The most obvious to me would be to place a binding to whatever controls the state of your background, and add a converter to return the correct brush, something like this:
public class BackgroundConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return new SolidColorBrush((bool)value ? Colors.Red : Colors.Blue);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Or you can place the class you already have as the main control in your view.
Another option is to create a MarkupExtension of your own, similar to binding with converter, if you'd like that bit of extra control over it.