UWP Binding to Visibility property of a FontIcon - c#

I'm trying to bind the Visibility property of a FontIcon to an enum property in my view model using a converter, but for some reason it throws an exception
Unable to cast object of type 'Windows.UI.Xaml.Controls.FontIcon' to type
'Windows.UI.Xaml.Data.Binding'
What I want to achieve is that depending on the current value of CurrentSortOrder hide or show an icon inside the MenuFlyoutItem
View model code:
public class TestViewModel : ViewModelBase
{
private TaskSortType _currentTaskSortOrder = TaskSortType.BY_NAME_ASC;
public TaskSortType CurrentSortOrder
{
get => _currentTaskSortOrder;
set => Set(ref _currentTaskSortOrder, value);
}
}
View:
<Page
x:Class="UWPTests.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="using:UWPTests.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:UWPTests"
xmlns:localModels="using:UWPTests.Models"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
DataContext="{x:Bind ViewModel}"
mc:Ignorable="d">
<Page.Resources>
<converters:TaskSortTypeToVisibilityConverter x:Key="TaskSortTypeToVisibilityConverter" />
</Page.Resources>
<Grid>
<AppBarButton Icon="Sort" Label="Sort">
<AppBarButton.Flyout>
<MenuFlyout>
<MenuFlyoutSubItem Text="By name">
<MenuFlyoutItem Text="Asc">
<MenuFlyoutItem.Icon>
<FontIcon Glyph="" Visibility="{Binding CurrentSortOrder, Mode=OneWay, Converter={StaticResource TaskSortTypeToVisibilityConverter}, ConverterParameter={x:Bind localModels:TaskSortType.BY_NAME_ASC}}" />
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
<MenuFlyoutItem Text="Desc">
<MenuFlyoutItem.Icon>
<FontIcon Glyph="" Visibility="Collapsed" />
</MenuFlyoutItem.Icon>
</MenuFlyoutItem>
</MenuFlyoutSubItem>
</MenuFlyout>
</AppBarButton.Flyout>
</AppBarButton>
</Grid>
Converter:
public class TaskSortTypeToVisibilityConverter : IValueConverter
{
public Visibility OnTrue { get; set; }
public Visibility OnFalse { get; set; }
public TaskSortTypeToVisibilityConverter()
{
OnFalse = Visibility.Collapsed;
OnTrue = Visibility.Visible;
}
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is null || parameter is null)
return Visibility.Collapsed;
var currentOrder = (TaskSortType)value;
var targetOrder = (TaskSortType)parameter;
return currentOrder == targetOrder ? OnTrue : OnFalse;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
if (value is Visibility == false)
return DependencyProperty.UnsetValue;
if ((Visibility)value == OnTrue)
return true;
else
return false;
}
}
Any help would be appreciated
Edit:
I get the exception here: this.InitializeComponent();
public sealed partial class MainPage : Page
{
public TestViewModel ViewModel { get; set; }
public MainPage()
{
ViewModel = new TestViewModel();
this.InitializeComponent();
}
}
Edit 2:
public enum TaskSortType
{
BY_NAME_ASC = 0,
BY_NAME_DESC = 1,
BY_UPDATED_DATE_ASC = 2,
BY_UPDATED_DATE_DESC = 3,
}

It seems that i cant use x:Bind directly in the ConverterParameter .. So i ended with the following:
I added in my page resources:
<localModels:TaskSortType x:Key="TaskSortByNameAsc">BY_NAME_ASC</localModels:TaskSortType>
<localModels:TaskSortType x:Key="TaskSortByNameDesc">BY_NAME_DESC</localModels:TaskSortType>
<localModels:TaskSortType x:Key="TaskSortByUpdatedDateAsc">BY_UPDATED_DATE_ASC</localModels:TaskSortType>
<localModels:TaskSortType x:Key="TaskSortByUpdatedDateDesc">BY_UPDATED_DATE_DESC</localModels:TaskSortType>
And then i replaced the ConverterParameter binding with the following:
<FontIcon Glyph="" Visibility="{Binding CurrentSortOrder, Mode=OneWay, Converter={StaticResource TaskSortTypeToVisibilityConverter}, ConverterParameter={StaticResource BY_NAME_ASC}}" />
Another workaround would be to pass the corresponding value in the ConverterParameter, for example ConverterParameter=0 or ConverterParameter="BY_NAME_ASC"and the cast that parameter to the corresponding enum value

Related

Bind checkbox to label fontweight with value converter

I want to make label that have FontWeight attribute dependent on checkbox.
If checkbox is checked then label font weight is bold, if not - then font weight is normal. I decided to bind FontWeight attribute in Label with Checkbox and add converter.
Here is screen what I want to achieve (text on bottom should be bolded when Bold is checked)
enter image description here
I created all the logic and binding but when I click on Bold checkbox nothing happends.
Here is what I have so far
MainWindow.xaml
<Window ...
...
<Window.Resources>
<local:BoolToStringConverter x:Key="custom" TrueValue="Bold" FalseValue="Normal" />
</Window.Resources>
<Grid>
...
<CheckBox Name="BoldField" Margin="5" FontWeight="Bold">Bold</CheckBox>
<Label Name="text" Grid.Row="3" Grid.ColumnSpan="3"
FontWeight="{Binding BoldField, Converter={StaticResource custom}}" />
...
</Window>
MainWindow.xaml.cs
namespace Wpf03
{
public class BoolToValueConverter<T> : IValueConverter
{
public T FalseValue { get; set; }
public T TrueValue { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null)
return FalseValue;
else
return (bool)value ? TrueValue : FalseValue;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value != null ? value.Equals(TrueValue) : false;
}
}
public class BoolToStringConverter : BoolToValueConverter<FontWeight> { }
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
I tried with different converters but no result. Why my converter is never called? Any ideas?
You are passing the Name of the CheckBox as the Binding Path
instead you need to specify the IsChecked property:
<CheckBox Name="BoldField" Margin="5" FontWeight="Bold">Bold</CheckBox>
<Label Name="text" Grid.Row="3" Grid.ColumnSpan="3"
FontWeight="{Binding ElementName=BoldField, Path=IsChecked, Converter={StaticResource custom}}" />

WPF - Detect design mode in a Converter

I have a converter in which I want to be able to change the value to Visibility.Collapsed when in DesignMode. Right it is ignoring the GetIsInDesignMode.
Also, I am binding the VM via dependency injectio (prism)
Converter:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (DesignerProperties.GetIsInDesignMode(new DependencyObject()))
return Visibility.Collapsed;
if (value != null && value is AllowedSourceCode)
{
var allowedSourceCode = (AllowedSourceCode)value;
if (value == null)
return Visibility.Visible;
else if (allowedSourceCode.SupportedSourceCodes.Contains(allowedSourceCode.SelectedSourceCode))
{
return Visibility.Collapsed;
}
else
return Visibility.Visible;
}
return Visibility.Collapsed;
}
View:
<Canvas Visibility="{Binding SupportedSourceCodes,Converter={StaticResource AllowedSourcesConverter}}" Background="Gray" Opacity="0.9"
Grid.Row="0" Grid.Column="0" Grid.RowSpan="3" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Panel.ZIndex="5" >
xaml.cs:
public ACARSubLedgerUC(ACARSubLedgerVM vm)
{
InitializeComponent();
DataContext = vm;
}
What you're doing should work.
I'm guessing you have a viewmodel behind your window and using the converter on a binding to that viewmodel. Please make sure you are setting your data context in XAML and not in code, because if you are setting it in code your converter will never hit in design mode.
<Window x:Class="WpfApp1.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:local="clr-namespace:WpfApp1"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="525"
Height="350"
mc:Ignorable="d">
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
...
</Window>
This ensures bindings are updated at design time and hence your converter will be called.
I wrote a markup extension that makes it way easier to muck around with design time bindings and property values without messing around with the code your view binds to or changing the actual runtime value of a property. The full write up is here: http://www.singulink.com/CodeIndex/post/wpf-visibility-binding-with-design-time-control
It works with visibility as well as most other properties. Usage looks like:
<Grid Visibility="{data:Value {Binding RootObject, Converter={StaticResource NullToVisibilityConverter}}, DesignValue=Visible}">
<TextBlock Background="Red" Text="Testing visibility" />
</Grid>
The extension class:
public class ValueExtension : MarkupExtension
{
public object DesignValue { get; set; } = DependencyProperty.UnsetValue;
[ConstructorArgument("value")]
public object Value { get; set; } = DependencyProperty.UnsetValue;
public ValueExtension() { }
public ValueExtension(object value)
{
Value = value;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
var provideValueTarget = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
var target = provideValueTarget.TargetObject as FrameworkElement;
var value = DesignerProperties.GetIsInDesignMode(target) && DesignValue != DependencyProperty.UnsetValue ? DesignValue : Value;
if (value == DependencyProperty.UnsetValue || value == null)
return value;
if (value is MarkupExtension)
{
return ((MarkupExtension)value).ProvideValue(serviceProvider);
}
var property = provideValueTarget.TargetProperty as DependencyProperty;
if (property.PropertyType.IsInstanceOfType(value))
return value;
return TypeDescriptor.GetConverter(property.PropertyType).ConvertFrom(value);
}
}

Bind Bool to Visibility of TextBlock within a ListBox

I want to bind visibility of a textblock, within a listbox, to a bool value in my ViewModel. Binding works well to a textblock outside the listbox, but it isn't working to the textblock within the listbox. Please help!
xaml code:
<TextBlock x:Name="heading" Visibility="{Binding MyVb.Visible, Converter={StaticResource BoolToVisConverter}}" Width="480"/>
<ListBox x:Name="lstBani1" ItemsSource="{Binding Users}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock x:Name="tb1" Text="{Binding string1}" Visibility="{Binding MyVb.Visible, Converter={StaticResource BoolToVisConverter}}" Width="480"/>
<TextBlock x:Name="tb2" Text="{Binding string2}" Width="480"/>
<TextBlock x:Name="tb3" Text="{Binding string3}" Width="480"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
cs code:
public partial class Page1 : PhoneApplicationPage
{
public Page1()
{
ViewModel model = new ViewModel();
model.Users = GetUsers();
model.MyVb = new MyVisibility();
model.MyVb.Visible = false;
this.DataContext = model;
}
// View Model
public class ViewModel
{
public List<User> Users { get; set; }
public MyVisibility MyVb { get; set; }
}
// Bool to Visibility Converter
public class BooleanToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool)value ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
// Property Classes
public class User
{
public string string1 { get; set; }
public string string2 { get; set; }
public string string3 { get; set; }
}
public class MyVisibility : INotifyPropertyChanged
{
private bool _Visible;
public event PropertyChangedEventHandler PropertyChanged;
public bool Visible
{
get { return _Visible; }
set
{
_Visible = value;
NotifyPropertyChanged("Visible");
}
}
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
private List<Bani> GetUsers() {....}
}
Your ListBox is bound to Users, therefore each item in the list is has a DataContext bound to a User. Therefore your binding is trying to look for the property in the User class, not the parent data context.
Give your page a Name and change your binding to the following:
Visibility="{Binding DataContext.MyVb.Visible, ElementName=yourPageName, Converter={StaticResource BoolToVisConverter}}"

Access public property of silverlight user control in code behind

I have created a simple Silverlight user control and defined a public property AllowMultiple.
public bool AllowMultiple { get; set; }
Now, I am setting this public property in XAML as follows:
<Controls1:PeopleChooser Name="SinglePeopleChooser" AllowMultiple="False" Width="Auto" d:LayoutOverrides="Height"/>
<Controls1:PeopleChooser Name="MultiplePeopleChooser" AllowMultiple="True" Width="Auto" d:LayoutOverrides="Height"/>
I want to know, which is the best event I can get the value of this public property. I am trying to get this value inside of the constructor and trying to hide/show some controls inside my user controls but its not working.
public PeopleChooser()
{
InitializeComponent();
if (AllowMultiple)
{
UsersListBox.Visibility = System.Windows.Visibility.Visible;
UserTextBox.Visibility = System.Windows.Visibility.Collapsed;
ResolveButton.Visibility = Visibility.Collapsed;
}
else
{
UsersListBox.Visibility = System.Windows.Visibility.Collapsed;
UserTextBox.Visibility = System.Windows.Visibility.Visible;
ResolveButton.Visibility = Visibility.Visible;
}
}
Probably because during constructor initialization the value of this public property has not been assigned by framework to the object.
I was able to solve it through loaded event. There is no need for dependency property. Please see the code below. I can access the properties value successfully in Loaded event.
public PeopleChooser()
{
this.Loaded += PeopleChooser_Loaded;
InitializeComponent();
}
void PeopleChooser_Loaded(object sender, RoutedEventArgs e)
{
if (AllowMultiple)
{
UsersListBox.Visibility = System.Windows.Visibility.Visible;
UserTextBox.Visibility = System.Windows.Visibility.Collapsed;
ResolveButton.Visibility = Visibility.Collapsed;
}
else
{
UsersListBox.Visibility = System.Windows.Visibility.Collapsed;
UserTextBox.Visibility = System.Windows.Visibility.Visible;
ResolveButton.Visibility = Visibility.Visible;
}
Convert the public property with a backfield,
private bool _allowMultiple;
public bool AllowMultiple
{
get { return _allowMultiple; }
set { _allowMultiple = value; }
}
Place a break point in the setter and check is it hits on Constructor, if not you can use the Loaded event to check the same and make use of that.
If you use a dependency property, you can bind other elements properties to the AllowMultiple property of the people chooser and use a visibility converter to show/hide them. Example:
public partial class PeopleChooser : UserControl
{
public PeopleChooser()
{
InitializeComponent();
}
public static readonly DependencyProperty AllowMultipleProperty = DependencyProperty.Register("AllowMultiple", typeof(bool), typeof(PeopleChooser), null);
public bool AllowMultiple
{
get { return (bool)GetValue(AllowMultipleProperty); }
set { SetValue(AllowMultipleProperty, value); }
}
}
<UserControl x:Class="TestSilverlightApplication.MainPage"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400"
xmlns:lcl="clr-namespace:TestSilverlightApplication">
<UserControl.Resources>
<lcl:VisibilityConverter x:Key="VisibilityConverter" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<StackPanel Orientation="Vertical">
<Button Click="Button_Click" Content="Toggle allow multiple" />
<lcl:PeopleChooser x:Name="lclPeopleChooser" AllowMultiple="False"></lcl:PeopleChooser>
<TextBlock Text="Dependent content" Visibility="{Binding AllowMultiple, ElementName=lclPeopleChooser, Converter={StaticResource VisibilityConverter}}" />
</StackPanel>
</Grid>
</UserControl>
private void Button_Click(object sender, RoutedEventArgs e)
{
lclPeopleChooser.AllowMultiple = !lclPeopleChooser.AllowMultiple;
}
public class VisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool parsedValue = false;
bool.TryParse(value.ToString(), out parsedValue);
if (parsedValue)
{
return Visibility.Visible;
}
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
This way you can avoid page events and potentially bind the AllowMultiple property to a view model property and let the UI take care of itself.

Problem with DataBinding in mvvm

I have big problem with databinding.
I cant bind data to children control. I'm really newbie in MVVM and I spend a lot of hours at this example and I have no idea what is wrong with this Code.
Little explanation:
I have MainWindow. It has UserControl to display list of todo.
I want to set my MyWindow class ParentViewModel as DataContext.
DataContext has TodoItemModelView as subdatacontext which must be datacontext of UserControlTodoItems.
<Window x:Class="Repo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Repo="clr-namespace:Repo" Title="Window1" Height="300" Width="300">
<Window.Resources>
<Repo:ParentViewModel x:Key="parentVM"/>
</Window.Resources>
<Window.DataContext>
<StaticResourceExtension ResourceKey="parentVM"/>
</Window.DataContext>
<Grid>
<Repo:UserControlTodoItems DataContext="{Binding Path=todoItemModelView}">
</Repo:UserControlTodoItems>
</Grid>
</Window>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
class ParentViewModel
{
public TodoItemModelView todoItemModelView { get; set; }
public ParentViewModel()
{
this.todoItemModelView=new TodoItemModelView();
}
}
public class TodoItemModelView
{
public ObservableCollection<TodoItem> todoItems { get; set; }
public TodoItemModelView()
{
ObservableCollection<TodoItem> loadedTodoItems = new ObservableCollection<TodoItem>();
loadedTodoItems.Add(new TodoItem() { Code = "10", ObjectCode = "DE", ObjectType = ObjectType.Country, Status = TodoItemStatus.InProgress, Type = TodoItemType.CollectPhotos });
loadedTodoItems.Add(new TodoItem() { Code = "11", ObjectCode = "DE", ObjectType = ObjectType.Country, Status = TodoItemStatus.Todo, Type = TodoItemType.DescribeOjbect });
loadedTodoItems.Add(new TodoItem() { Code = "12", ObjectCode = "DE", ObjectType = ObjectType.Country, Status = TodoItemStatus.Accomplshed, Type = TodoItemType.CollectVideos });
todoItems = loadedTodoItems;
}
}
<UserControl x:Class="Repo.UserControlTodoItems"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Repo="clr-namespace:Repo" Height="auto" Width="auto">
<UserControl.Resources>
<Repo:TodoItemStatusConverter x:Key="TodoItemStatusConverter"/>
</UserControl.Resources>
<Grid>
<ListBox ItemsSource="{Binding Path=todoItems}" Name="lbTasks">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=Status, Converter={StaticResource TodoItemStatusConverter}}"/>
<TextBlock Text="{Binding Path=Type}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</UserControl>
public UserControlTodoItems()
{
InitializeComponent();
}
I correct this.
I must add one question:
is there any simple way to inform parentmodel, of change checkbox at listbox?
this is a converter:
public class TodoItemStatusConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
TodoItemStatus todoItemStatus = (TodoItemStatus)value;
if (todoItemStatus == TodoItemStatus.Accomplshed)
{
return true;
}
else
{
return false;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if ((bool) value)
{
return TodoItemStatus.Accomplshed;
}
else
{
return TodoItemStatus.InProgress;
}
}
This is class TodoItem:
public class TodoItem
{
public TodoItemType Type { get; set; }
public TodoItemStatus Status { get; set; }
public string Code { get; set; }
public string ObjectCode { get; set; }
public ObjectType ObjectType { get; set; }
}
Why is the binding for your "lbTasks" Listbox just "{Binding}" and not "{Binding Path=todoItems}"
I'm really taking a quick glance at your code here.. you seem to be passing the todoItemModelView as a DataContext properly, but never inform the listbox where in that data context it will find its items.
You may also want to use an ObservableCollection for the list in the VM so you can add and remove todo's in a way the GUI can respond to
<CheckBox IsChecked="{Binding Path=Status, Converter={StaticResource TodoItemStatusConverter}}"/>
This implies there is a property on ToDoItemViewModel called Status - but there isn't! Rethink your ToDoItemVm class to just be a wrapper for a toDoItem (ie, public ToDoItemVm(ToDoItem model) and get that array of items into the PArentVm (do use ObservableCollection and bind it to the list box. Also add a SelectedToDoItem property on the ParentVm. So your binding for the list box includes something like
ItemsSource="{Binding ToDoTems}"
IsSynchronizedWithCurrentItem="True"
SelectedItem="{Binding SelectedToDoItem, Mode=TwoWay}"
Then expose that Status property on your ToDoItemVm, have the class implement INPC, and raise PropertyChanged in the setter.
It may take some work to sort it out, so feel free to ask more questions as you go. The converter idea is fine.
HTH,
Berryl

Categories