Metro/Windows Store App: Can't use Foreground as TemplateBinding source in ControlTemplate? - microsoft-metro

I'd like to use the value of the Foreground of my control to be used as source for a VisualState ColorAnimation in a ControlTemplate.
My template definition looks mainly like the standard template for a ToggleButton, with some mods (marked as <<<.....>>>):
<Style TargetType="ToggleButton>
<Setter .../>
...
<<< <Setter Property="Foreground" Value="#FF000000"/> >>>
...
<Setter .../>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState .../>
<VisualState x:Name="PointerOver">
<Storyboard>
<ColorAnimation Duration="0" Storyboard.Target="BackgroundGradient" Storybord.TargetProperty="(Rectangel.Fill).(GradientBrush.GradientStop)[1].(GradientStopColor)" <<< To="{Binding Foreground, RelativeSource={RelativeSource TemplatedParent}}" >>> />
</Storyboard>
</VisualState>
...
...
...
</Style>
...
...
<ToggleButton <<< Foreground="#FFFF0000" >>> ...../>
So I expected to see the animation use the set foreground color (#FFFF0000) as part of the mouse over animation, but it did nothing at all.
When I write To="#FFFF0000" in the animation definition, I get the expected result, but I'd like to keep the animation color dynamic and different for each ToggleButton in my app.
Any idea how to fix this?
Please!
Edit: After trying to achieve a similar effect as above by adding a new Rectangle with a LinearGradientBrush to the ContentPresenter where one GradientStop should be bound to {TemplateBinding Foreground}, I now get an error that might enlighten the reason for my problem "Object of type 'Windows.UI.xaml.DependencyProperty' cannot be converted to type 'System.Windows.DependencyProperty'."
As it seems {TemplateBinding ...} produces a wrongly typed DependencyProperty or GradientStop expects a wrong type in Windows Store Apps.
However! Is there a way to overcome this by explicit type cast in XAML or any other workaround?
Thanks

So, I finally worked it out myself.
After getting an error message (see "Edit" of original post) I tried crafting a suitable converter and now I could kick myself, for not having seen the obvious!
It had nothing to do with WinRT or TemplateBinding or incompatibilities between Windows.UI.Xaml <-> and System-Windows objects.
I just did not realize that Foreground is a Brush type (in my case a SolidColorBrush) while GradientStopColor expects a Color!
Once I created a BrushToColorConverter and used this in connection with RelativeSource binding (since TemplateBinding does not allow converters) it worked.
XAML:
<Page.Resources>
<local:BrushToColorConverter x:Key="Brush2Color" DefaultOpacity="1.0"/>
</Page.Resources>
...
<ColorAnimation Duration="0"
Storyboard.TargetName="BackgroundGradient"
Storyboard.TargetProperty="(Rectangle.Fill).(GradientBrush.GradientStops)[1].(GradientStop.Color)"
To="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},
Path=Foreground,
Converter={StaticResource Brush2Color},
ConverterParameter=0.5}"
/>
CodeBehind:
public class BrushToColorConverter : IValueConverter
{
private double defaultOpacity = 1;
public double DefaultOpacity
{
set { defaultOpacity = value; }
}
public object Convert(object value, Type targetType, object parameter, string culture)
{
SolidColorBrush brush = value as SolidColorBrush;
double opacity;
if (!Double.TryParse((string)parameter, out opacity))
opacity = defaultOpacity;
if (brush == null)
return Colors.Transparent;
else
return Color.FromArgb((byte)(255.0 * brush.Opacity * opacity),
brush.Color.R,
brush.Color.G,
brush.Color.B
);
}
public object ConvertBack(object value, Type targetType, object parameter, string culture)
{
return null;
}
}

Related

Getting background color from view model property

Is it possible to get a control’s background color from a property in ViewModel?
The purpose is to be able to change color of Border or Button based on user actions. Because I’m using the MVVM approach, ideally, I simply set the background color of my control through a property in my view model.
I tried the following but it didn’t work:
<Border
BackgroundColor="{Binding MyBorderBackgroundColor}">
<Label Text=“Hello World” />
</Border>
In my view model, I use a string property and set it to either a color name such as Red or a Hex value such as #FF0000. I have an Init() method that I call from OnAppearing() and set the value -- see below:
[ObservableProperty]
string myBorderBackgroundColor;
...
public void Init()
{
MyBorderBackgroundColor = "Red"; // Or Hex value => MyBorderBackgroundColor = "#FF0000";
}
The app simply ignores the color setting and defaults to page background. No error but just doesn't use the value set through the view model property.
Any suggestions?
Your backing property is a string, while it should be a Color. However, I do understand you would think this works, because in XAML you can simply add a string to it.
Background
To understand why that works in XAML but not code, we'll have to learn about TypeConverter. Since XAML can only contain strings, we have to find a way to convert that string into a type that is actually useable. With a TypeConverter we do just that.
On an object where we expect people to use a string in XAML but still expect the desired outcome we add this attribute, here it is for Color:
[TypeConverter(typeof(Converters.ColorTypeConverter))]
public class Color
{
/// The class
}
Here is the full implementation of ColorTypeConverter in case you're curious.
This means that when the XAML parser finds this property and the corresponding (string) value, it will invoke that TypeConverter first and that results into the concrete type, in our case Color, to be used.
When you do a binding like you're doing, the TypeConverter is not invoked, and thus we're now binding a string value to a property that expects a Color object and therefore: not working.
The Fix
To fix this, change the backing property to a Color and initialize it as such.
[ObservableProperty]
Color myBorderBackgroundColor;
// ...
public void Init()
{
MyBorderBackgroundColor = Colors.Red; // Or Hex value => MyBorderBackgroundColor = Color.FromArgb("#FF0000");
}
Your XAML contains a small error with no quotes around the binding. Not sure if that is in your actual code, but just to be sure. Make sure there is quotes around the value of BackgroundColor like so:
<Border
BackgroundColor="{Binding MyBorderBackgroundColor}">
<Label Text="Hello World" />
</Border>
First of all, you need to make sure that the binding is set up correctly using double quotes:
<Border
BackgroundColor="{Binding MyBorderBackgroundColor}">
<Label Text=“Hello World” />
</Border>
Then, I would recommend using the Color type in the ViewModel instead of a string since there is no implicit conversion between those types. That's how I am doing it in my own apps, too.
So, you could change your code to the following and define the color in a variety of different ways (the list is not final):
[ObservableProperty]
string myBorderBackgroundColor;
//...
public void Init()
{
// use hex value
MyBorderBackgroundColor = Color.FromArgb("#FF0000");
// parse string
MyBorderBackgroundColor = Color.Parse("Red");
// use RGB
MyBorderBackgroundColor = Color.FromRgb(255,0,0);
// use named color
MyBorderBackgroundColor = Colors.Red;
//...
}
You can find more information about Colors in the official documentation.

How to pass objects to PopupPage using MAUI.Toolkit or Mopup plugins?

I have a Maui app where I use MVVM pattern with MAUI Toolkik and also trying with Mopup plugin but I haven't found how to pass objects to Popup pages combined with MVVM. At the moment, I have a page, which I use to navigate to the PopupPage successfully and also I am able to connect the PopupPage with its viewmodel. What I am unable to do is to pass any kind of object to the PopupPage.
I have tried to set the PopupPage constructor with parameters but the methods to navigate to the PopupPage only recognize parameters setted on the code behind.
Here is my code:
Popup
<mopup:PopupPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="NewScholarApp.Views.MessagePopup"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
xmlns:mopup="clr-namespace:Mopups.Pages;assembly=Mopups"
xmlns:viewmodels="clr-namespace:NewScholarApp.ViewModels"
x:DataType="viewmodels:MessagePopupViewModel">
<mopup:PopupPage.BindingContext>
<viewmodels:MessagePopupViewModel/>
</mopup:PopupPage.BindingContext>
<VerticalStackLayout BackgroundColor="White" HorizontalOptions="Center" VerticalOptions="Center" HeightRequest="100" WidthRequest="100">
<Label
Text="{Binding Message}"
VerticalOptions="Center"
HorizontalOptions="Center" />
</VerticalStackLayout>
</mopup:PopupPage>
I use this to navigate from my page viewmodel
await _popupNavigation.PushAsync(new MessagePopup(string text = "tex"));
If I try to set a parameter, shows this error, even that in my PopupPage constructor I have setted a parameter
"MessagePopup does not contain a a constructor that contains 1 argument"
**MessagePopupViewModel **
public partial class MessagePopupViewModel : ObservableObject
{
#region AnP
[ObservableProperty]
private string message;
private readonly IApiService _apiService;
#endregion
public MessagePopupViewModel(string tex)
{
Message = tex;
}
}
you are navigating using this code
await _popupNavigation.PushAsync(new MessagePopup(string text = "tex"));
so MessagePopup MUST have a constructor that accepts a parameter
public MessagePopup(string somevalue)
if you also want to pass that value to your VM, then you can add
BindingContext = new MessagePopupViewModel(somevalue);
if you do this, then you should remove the BindingContext property from the XAML

Should ItemSource and BindingContext both be set when using MVVM (Xamarin.Froms ListView)?

Model:
public class Question : INotifyPropertyChanged
{
private float? _answer;
public float? Answer
{
get => _answer;
set
{
_answer = value;
NotifyPropertyChanged();
}
}
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
View model:
public class QuestionViewModel
{
private ObservableCollection<Question> _questions;
public ObservableCollection<Question> Questions
{
get => _questions;
set
{
if (_questions != value)
{
_questions = value;
}
}
}
}
XAML:
<ListView x:Name="ListViewQuestions" SelectionMode="Single" HasUnevenRows="True" HeightRequest="250" VerticalOptions="FillAndExpand">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Entry x:Name="EntryAnswer" Text="{Binding Answer,Mode=TwoWay}" Keyboard="Numeric" FontSize="Medium" VerticalOptions="End"
HorizontalOptions="FillAndExpand" Grid.Row="0" Grid.Column="1" >
<Entry.Behaviors>
<behaviors:EntryMaxValueBehavior MaxValue="{Binding MaxVal}" BindingContext="{Binding BindingContext, Source={x:Reference EntryAnswer}}" />
<behaviors:EntryMinValueBehavior MinValue="{Binding MinVal}" BindingContext="{Binding BindingContext, Source={x:Reference EntryAnswer}}" />
</Entry.Behaviors>
</Entry>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
In my page OnAppearing method, I set the ListViewQuestions like this:
var questions = await DataStore.GetQuestions(_inspection.Id);
var questionsViewModel = new QuestionViewModel { Questions = new ObservableCollection<Question>(questions) };
ListViewQuestions.ItemsSource = null;
ListViewQuestions.ItemsSource = questionsViewModel.Questions;
However, when values are entered into EntryAnswer, the setter in the Question model is not called, as I would expect. I thought that maybe this was because the BindingContext for the ListView needed to be set, so I set it like this:
ListViewQuestions.BindingContext = questionsViewModel;
However, the setter in the Question model is still not called. I also tried implementing INotifyPropertyChanged in the QuestionViewModel, but still no joy. I checked that the ObservableCollection in the View Model is set correctly, with actual data, and it is. Can anyone spot what might be going wrong here?
Edit 1: I also tried not setting the ItemSource, but only setting the ListViewQuestions.BindingContext to the view model, but then the ListView was not being populated with any data.
Here is how this works together.
The BindingContext is the object that will be the scope for whatever bindings that are in the page or it's children, unless you specify a different context for a certain child object, but let's not overcomplicate things for now.
This means, that when you have set the BindingContext, all Bindings will now start looking into the object referenced in the BindingContext. In your case, you set the BindingContext to an instance of QuestionViewModel.
You want your ListView, to get its items from the QuestionViewModel.Questions property. So, you set a binding like this:
<ListView x:Name="ListViewQuestions" ItemsSource="{Binding Questions}" ...>.
Questions needs to be a public property in the BindingContext, in our case QuestionViewModel. You got this right already.
Now, whenever you assign something to Questions this should also propagate to your ListView because of the binding.
Inside your ListView you are using a ViewCell, now note, that the scope does change here. Each cell represents an instance of an object inside the ItemsSource. In our case, each cell will hold a Question. You are using this:
<Entry x:Name="EntryAnswer" Text="{Binding Answer,Mode=TwoWay}" ...>
This means Answer needs to be a public property inside Question. You got this right already.
When you implement it like this, basically the only thing you do is fill your view model and assign that to the BindingContext of your page. If you are using an MVVM framework, this might happen automatically.
At some point, you might run into some trouble that the UI doesn't update and you will have to implement the INotifyPropertyChanged interface. Have a close look at what object doesn't update on screen and implement the interface on that object along with the needed plumbing, but from what I can see in this code, this isn't needed right now. And besides, you have implemented it the right way in your Question right now.
I hope this makes sense! It's a bit hard to wrap your head around the first time, but once you get the swing of it, it is pretty easy!
In your Answer Setter try:
set
{
float? temp = null;
if(float.TryParse(value, out temp)
{
_answer = temp;
NotifyPropertyChanged("Answer");
}
}
It seems like for this to work though your setter would have to be called, and you indicate that it is not, so I think it must be the min, max binding where this is kicking out the error. For now perhaps get rid of that and see if the setter will get called.
In WPF using a converter is typical and I think will work with the Xamarin as well. See this for a good example of how to implement IValueConverter.

Binding a View to my main window only shows me the type

I'm trying to bind a View to my ContentControl. Currently, it just shows me the type (eg NameSpace.ViewModel.MainWindowViewModel)
Although I will point out, I'm not sure if I'm approaching this correctly.
My simple set up is I have a View (UserControl) which is empty other than a single control (which has been placed just for the visual).
My MainWindow.xaml
<Window x:Class="DelegateGoodExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModel="clr-namespace:DelegateGoodExample.ViewModel"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<viewModel:MainWindowViewModel x:Key="Vm" />
</Window.Resources>
<Grid>
<ContentControl Height="147" Margin="53,132,60,0"
VerticalAlignment="Top"
Content="{StaticResource Vm}" />
</Grid>
</Window>
(There is nothing in the code behind).
My MainWindowViewModel.cs
namespace DelegateGoodExample.ViewModel
{
public class MainWindowViewModel
{
private object _currentView;
public object CurrentView
{
get { return new View.QuickView(); }
set { _currentView = value; }
}
}
}
So, my question is,
Do I have to set a datacontext in this instance (and even if I do add it the results persist)?
What have I done wrong?
You are putting a viewmodel inside the ContentControl, not a view. Since your viewmodel class is not a UIElement and there is no DataTemplate to determine how it should be rendered, what gets displayed is simply its .ToString() representation.
An immediate fix would be:
<ContentControl Height="147" Margin="53,132,60,0"
VerticalAlignment="Top"
Content="{Binding Source={StaticResource Vm}, Path=View}" />
However, instead of doing things this way you should be putting your view inside the Grid directly, and the viewmodel should not have any knowledge of the view.

How to set different localized string in different visual states in WP7 using Blend?

How do I set different localized strings in different visual states in WP7 using Blend without any code behind?
I can set different non-localized strings in different visual states (although it flickers). That works, but how about localized strings?
If I change the string using data binding in Blend, Blend just overrides the data binding in Base state and not the actual state where I'm recording.
EDIT:
This is how I localize my strings:
I have a resources file named AppPresources.resx. Then I would do this in code:
// setting localized button title
mainButton.Content = AppResources.MainButtonText;
Then I have a GlobalViewModelLocator from MVVM Light Toolkit with the following Property for Databinding.
private static AppResources _localizedStrings;
public AppResources LocalizedStrings
{
get
{
if (_localizedStrings == null)
{
_localizedStrings = new AppResources();
}
return _localizedStrings;
}
}
And in xaml file:
<Button x:Name="mainButton" Content="{Binding LocalizedStrings.MainButtonText, Mode=OneWay, Source={StaticResource Locator}}" ... />
What you need to do, is very close to what you're already doing. First, define a class named Resources.cs with following content
public class Resources
{
private static AppResources resources = new AppResources();
public AppResources LocalizedStrings
{
get
{
return resources;
}
}
}
This allows us to create a instance of your Resource File in XAML. To do this, open App.xaml and add following
<Application.Resources>
<local:Resources x:Key="Resources" />
</Application.Resources>
Now when you need to do bindings in your XAML, you do it like this:
<Button Content="{Binding LocalizedStrings.MainButtonText,
Source={StaticResource Resources}}" />
What you'll notice is that it doesn't work in Blend, yet. To make it work in Expression Blend,
add the following file: DesignTimeResources.xaml in the Properties Folder, and add following content
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:YourNameSpace">
<local:Resources x:Key="Resources" />
</ResourceDictionary>
Now, you press F6 in Visual Studio to recompile, and voila, your localized strings are available in Expression Blend!
A real-world example from one of my projects:
AppResources.cs
DesignTimeResources.xaml
App.xaml