How to bind data to a control's Visibility property - c#

I understand that the Visibility property of a control cannot be bound to data in the same way that other properties can. It needs some kind of converter(?). In trying to implement the solution from this question I run into a compiler error that says: The resource "BoolToVisible" could not be resolved. I'm guessing that I have to create a ResourceKey named BoolToVisible, I just don't know how.
I'm requesting that someone show me the right way to Bind to the Visibility property of a control.
*The control that I am adding this to is a radio button.
* I have a bool property for isVisible in my Data Model that will be bound to this radio button.
Data Model Property:
private bool _isVisible = true;
public bool IsVisible
{
get { return _isVisible; }
set
{
_isVisible = value;
NotifyPropertyChange(() => IsVisible);
}
}
XAML:
<RadioButton Visibility="{Binding DataModel.IsVisible,Converter={StaticResource ResourceKey=BoolToVisible},RelativeSource={RelativeSource TemplatedParent}}" ... />
Thank you.

2 examples :
The first using a Converter like stated in the question :
public class BooleanToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null || !(value is bool))
return Binding.DoNothing;
return (bool)value ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
}
in xaml :
<Window x:Class="Stackoverflow.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
xmlns:local="clr-namespace:Stackoverflow"
>
<Window.Resources>
<local:BooleanToVisibilityConverter x:Key="booleanToVisibiltyConverter"/>
</Window.Resources>
<Grid>
<Button Visibility="{Binding IsSomeThing,Converter={StaticResource booleanToVisibiltyConverter}}"/>
</Grid>
the second :
in your DataContext you can literly hold a Visibility Property
cs :
private Visibility _myControlVisibility;
public Visibility MyControlVisibility
{
get { return _myControlVisibility; }
set { _myControlVisibility = value; }
}
xaml :
<Button Visibility="{Binding MyControlVisibility}"/>

You can binding visibility with a property, you just need to have a Dependency Property of Visibility field as:
Public Property MyVisibility As Windows.Visibility
Get
Return GetValue(MyVisibilityProperty)
End Get
Set(ByVal value As Windows.Visibility)
SetValue(MyVisibilityProperty, value)
End Set
End Property
Public Shared ReadOnly MyVisibilityProperty As DependencyProperty = _
DependencyProperty.Register("MyVisibility", _
GetType(Windows.Visibility), GetType(MyWindow), _
New PropertyMetadata(Nothing))
Then do the binding as the usual (The code is in VB).
Remember that in the New PropertyMetadata you can set an initial state for the object eg:
Public Shared ReadOnly MyVisibilityProperty As DependencyProperty = _
DependencyProperty.Register("MyVisibility", _
GetType(Windows.Visibility), GetType(MyWindow), _
New PropertyMetadata(Windows.Visibility.Hidden))

Related

uwp selectedIndex binding with enum not working twoway

so I have ComboBox with some items and its SelectedIndex is bound TwoWay with a property of type MyTypeEnum the idea is its selected index value can be set by an enum to int converter and when user changes selection on combobox itself then the new selectedIndex should update the value it is bound to. it is working fine OneWay i.e: from property to SelectedIndex, but not working reverse so with breakpoints I have confirmed that Set method of my bound property does not execute when I change selection of combobox however the ConvertBack method of my converter does execute which is like it should.
I have prepared a minimal and simple code repo to reproduce the issue : https://github.com/touseefbsb/ComboBoxToEnumBug
Code
MainPage.xaml
<Page
x:Class="App1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Page.Resources>
<local:IdToIndexConverter x:Key="IdToIndexConverter"/>
</Page.Resources>
<Grid x:DefaultBindMode="TwoWay">
<ComboBox SelectedIndex="{x:Bind ViewModel.MyTypeEnum, Converter={StaticResource IdToIndexConverter}}" >
<ComboBoxItem>item 1</ComboBoxItem>
<ComboBoxItem>item 2</ComboBoxItem>
<ComboBoxItem>item 3</ComboBoxItem>
</ComboBox>
</Grid>
MainViewModel
public class MainViewModel : Observable
{
private MyTypeEnum _myTypeEnum = MyTypeEnum.Type1;
public MyTypeEnum MyTypeEnum
{
get => _myTypeEnum;
set => Set(ref _myTypeEnum, value);
}
}
Observable
public class Observable : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void Set<T>(ref T storage, T value, [CallerMemberName]string propertyName = null)
{
if (Equals(storage, value))
{
return;
}
storage = value;
OnPropertyChanged(propertyName);
}
protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
MyTypeEnum
//using byte as parent so that I can start count from 1 instead of 0
public enum MyTypeEnum : byte
{
Type1 = 1,
Type2,
Type3
}
Converter
public class IdToIndexConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language) => System.Convert.ToInt32(value) - 1;
public object ConvertBack(object value, Type targetType, object parameter, string language) => ((int)value) + 1;
}
I have confirmed that Set method of my bound property does not execute when I change selection of combobox however the ConvertBack method of my converter does execute which is like it should.
Your ConvertBack method returns an int but it should return an MyTypeEnum.
The source property of the view model cannot be set to an int, only to a MyTypeEnum.
Cast int to enum in C#

Is it possible to dynamically change/remove the SwipeItems of a SwipeControl in UWP?

I'm trying to use Microsofts new SwipeControl to allows users to reveal an action that's available to the item. What I'm struggling with is removing the SwipeItems when the action is no longer available, I know it is possible as setting and nulling the LeftItems property on the SwipeControl works as I expect it to, but I need it to be more dynamic than this.
Some notes on setup, I'm using Caliburn Micro as the MVVM framework and its implementation of INotifyPropertyChanged. I'm not using x:Bind.
Attempt 1 - Control based on SwipeControl:
I tried creating a new control based on the SwipeControl. I added DepedencyProps for LeftItemsEnabled and InternalLeftItems. InternalLeftItems is used to store the list of Items between each removal and reset. To remove and reset the LeftItems property I use the LeftItemsEnabled property by changing the boolean. I set the LeftItems to my InternalLeftItems value or to null. See below for code:
Dependency Properties and PropertyChanged implementation
public static readonly DependencyProperty EnableLeftItemsProperty =
DependencyProperty.Register("EnableLeftItems", typeof(bool), typeof(UpdatedSwipeControl), new PropertyMetadata(false, LeftEnabledPropertyChanged));
public static readonly DependencyProperty InternalLeftItemsProperty =
DependencyProperty.Register("InternalLeftItems", typeof(SwipeItems), typeof(UpdatedSwipeControl), new PropertyMetadata(new SwipeItems()));
public SwipeItems InternalLeftItems {
get { return (SwipeItems)GetValue(InternalLeftItemsProperty); }
set { SetValue(InternalLeftItemsProperty, value); }
}
private static void LeftEnabledPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var control = d as UpdatedSwipeControl;
if (control == null) return;
control.EnableLeftItems = (bool)e.NewValue;
if ((bool)e.NewValue)
control.RightItems = control.InternalRightItems;
else
control.RightItems = null;
UpdateLayout();
}
Usage in View
<Page.Resources>
<SymbolIconSource x:Key="ButtonIconSource"
Symbol="Cancel" />
<SwipeItems x:Key="Items">
<SwipeItem Text="SWIPE ITEM"
IconSource="{StaticResource ButtonIconSource}"
Background="Red" />
</SwipeItems>
</Page.Resources>
<controls:UpdatedSwipeControl x:Name="swipeControl"
EnableLeftItems={Binding ItemsEnabled}
InternalLeftItems="{StaticResource Items}">
<TextBlock Text="SWIPE" />
</SwipeControl>
Property in ViewModel
private bool itemsEnabled;
public bool ItemsEnabled {
get {
return itemsEnabled;
}
set {
itemsEnabled = value;
NotifyOfPropertyChange(nameof(ItemsEnabled));
}
}
Problem with this attempt is the LeftEnabledPropertyChanged only fired once, when changing from the default value False to True on the initial bind. It doesn't fire again on any of the subsequent NotifyPropertyChanged's. I not sure if I've missed something in my setup of the DependencyProps or the PropertyChangedEvent.
Attempt 2 - Converter on LeftItems prop:
My second attempt was to use a Converter on the Binding of the boolean. I created a simple converter to return either SwipeItems in my resources or null. See below for code:
Converter code
public class ItemsConverter : IValueConverter {
public SwipeItems items { get; set; }
public object Convert(object value, Type targetType, object parameter, string language)
{
return (bool) value ? items : null;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
Implementation in view
<Page.Resources>
<sampleApp:ItemsConverter x:Key="converter" items="{StaticResource Items}"/>
<SwipeItems x:Key="Items">
<SwipeItem Text="SWIPE ITEM"
IconSource="{StaticResource ButtonIconSource}"
Background="Red" />
</SwipeItems>
</Page>
<SwipeControl x:Name="swipeControl"
LeftItems="{Binding ItemsEnabled, Converter={StaticResource converter}}">
<TextBlock Text="SWIPE" />
</SwipeControl>
Same implementation of the ItemsEnabled Property on the ViewModel.
The problem with this attempt was the converter was never hit and there were no Binding errors.
In summation I know it's possible to set/reset the SwipeItems property via code-behind which works ok in very simple scenarios. But anything more dynamic I can't get to work. Has anyone else run into this issue or have a workaround to this?

How to fire Property Changed with an Enum Property?

I have a standard Enum that will either be Yes or No:
public enum YesOrNo
{
Yes,
No
}
My base Model Class has a YesOrNo property like this:
public class Group : NotifyPropertyChanged
{
private YesOrNo groupOperator;
public YesOrNo GroupOperator
{
get
{
return this.groupOperator;
}
set
{
this.groupOperator = value;
OnPropertyChanged("GroupOperator");
}
}
In my View, I am using a ToggleSwitch, similar to a slider you would see on a Mobile phone. Sliding back and forth should effectively reassign the value of the Enum. So it will default as Yes and sliding the toggle will set the Enum value to No and alternatively.
If I were to have a test method that reassigns the Enum when the Checked command is hit, the PropertyChanged event is fired so I know that is technically working. I am just wondering how I could go about alternating values in the Enum.
This is the ToggleButton in my XAML:
<ToggleButton Style="{StaticResource ToggleViewSwitch}" Command="{Binding SetOperatorCommand, UpdateSourceTrigger=PropertyChanged}" IsChecked="{Binding IsChecked, UpdateSourceTrigger=PropertyChanged}"/>
And this is my Main View Model, where I hold the Command and the test method to assign the value manually:
private bool isChecked = false;
public bool IsChecked
{
get
{
return this.isChecked;
}
set
{
this.isChecked = value;
OnPropertyChanged("IsChecked");
}
}
private RelayCommand setOperatorCommand;
public ICommand SetOperatorCommand
{
get
{
if (this.setOperatorCommand == null)
{
this.setOperatorCommand = new RelayCommand(
x => ToggleGroupOperator());
}
return this.setOperatorCommand;
}
}
private void ToggleGroupOperator()
{
if (IsChecked)
{
TopLevelGroup.GroupOperator = YesNo.No;
}
else
{
TopLevelGroup.GroupOperator = YesNo.Yes;
}
}
First make a Converter...
public class YesOrNoToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
=> (value is YesOrNo yesOrNo && yesOrNo == YesOrNo.Yes) ? true : false;
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> (value is bool isYes && isYes) ? YesOrNo.Yes : YesOrNo.No;
}
Then reference the converter during binding...
<Window.Resources>
<Converters:YesOrNoToBooleanConverter x:Key="YesOrNoToBooleanConverter" />
</Window.Resources>
<Grid>
<CheckBox IsChecked="{Binding GroupOperator, Converter={StaticResource YesOrNoToBooleanConverter}}" />
</Grid>
This will allow your ViewModel to remain using the enum without any overhead and the view to bind without any overhead; leave this binding manipulation work to converters.

Custom TextBox user control implementing a converter in WPF

I am trying to build a custom user control, specifically a custom TextBox that converts the text entered by the user to uppercase as it is typed and displays it in the control. However, I cannot get this to work. Here is my code:
CustomTextBox UserControl:
<UserControl x:Class="SOFWpf.CustomTextBox"
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:converters="clr-namespace:SOFWpf.Converters"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<converters:CaseConverter x:Key="CaseConverter" />
</UserControl.Resources>
<TextBox Text="{Binding Path=., Converter={StaticResource CaseConverter}}"/>
UserControl's Code-Behind:
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(CustomTextBox), new UIPropertyMetadata(""));
Usage:
<local:CustomTextBox Text="a b c"/>
Converter:
public class CaseConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string text = value as string;
return text?.ToUpper();
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string text = value as string;
return text?.ToLower();
}
}
What do I need to change to make this custom TextBox work as intended?
You need to add binding Path=Text:
<TextBox Text="{Binding Path=Text, Converter={StaticResource CaseConverter}}"/>
And in the user control constructor set DataContext to this:
public CustomTextBox()
{
InitializeComponent();
DataContext = this;
}

How to Bind the Cursor property of a Grid to a Property of my ViewModel in Silverlight 3.0?

I'm attempting to bind an IsLoading property to the Cursor property of my UI's LayoutRoot Grid. I'm trying to have the main app cursor become an hourglass whenever the property says it's loading.
I'm binding the property as follows:
<Grid Cursor="{Binding IsLoading, Converter={StaticResource CursorConverter}}">
The key "CursorConverter" maps to the BoolToCursorConverter in the resources. The converter code is:
public class BoolToCursorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (parameter == null)
return ((bool)value == true) ? Cursors.Wait : Cursors.Arrow;
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
Cursor cursor = value as Cursor;
if (cursor != null)
return cursor == Cursors.Wait ? true : false;
return false;
}
}
When I try to run this though I get the XamlParseException "The given key was not present in the dictionary."
Any help would be appreciated, thanks,
Why you get that error
Do you have something like this in the content of a Resources property?
<local:BoolToCursorConverter x:Key="CursorConverter" />
If not then that's what is wrong but I'm guessing you already do.
In that case you I suspect you have placed it in the Resources property of the Grid it applies to. That would be why it can not be found. StaticResource are resolved immediately as the Xaml is parsed. Hence any key used must already be loaded into a resource dictionary prior to use. The Xaml parser knows nothing of the contents of the Grid's Resources property because it hasn't processed it yet. Hence:-
<UserControl>
<Grid Cursor="{Binding IsLoading, Converter={StaticResource CursorConverter}}">
<Grid.Resources>
<local:BoolToCursorConverter x:Key="CursorConverter" />
</Grid.Resources>
<!-- Contents here -->
</Grid>
</UserControl>
will fail. Where as:-
<UserControl>
<UserControl.Resources>
<local:BoolToCursorConverter x:Key="CursorConverter" />
</UserControl.Resources >
<Grid Cursor="{Binding IsLoading, Converter={StaticResource CursorConverter}}">
<!-- Contents here -->
</Grid>
</UserControl>
would at least not fail in finding the converter.
What you actually need to do
I've presented the above to answer your question but I can see it doesn't really help you. You can't bind to the Cursor property like this. (It doesn't expose a public identifier field, Xaml uses the NameOfThing + "Property" convention to find a field that is the DependencyProperty for the property being bound).
The solution is to create an Attached property:-
public class BoolCursorBinder
{
public static bool GetBindTarget(DependencyObject obj) { return (bool)obj.GetValue(BindTargetProperty); }
public static void SetBindTarget(DependencyObject obj, bool value) { obj.SetValue(BindTargetProperty, value); }
public static readonly DependencyProperty BindTargetProperty =
DependencyProperty.RegisterAttached("BindTarget", typeof(bool), typeof(BoolCursorBinder), new PropertyMetadata(false, OnBindTargetChanged));
private static void OnBindTargetChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
FrameworkElement element = sender as FrameworkElement;
if (element != null)
{
element.Cursor = (bool)e.NewValue ? Cursors.Wait : Cursors.Arrow;
}
}
}
Now you can actually do the binding like this:-
<Grid local:BoolCursorBinder.BindTarget="{Binding IsLoading}">

Categories