Adding BasedOn Style on App.xaml is crashing on App() { InitializeComponent(); } - xaml

Adapting an project to Template10, I go into inherited styles in App.xaml are crashing.
It looks like Template10, doesn´t support inherited or extended styles. I was trying to extend SubTitleStyle from TitleStyle but I get an COM exceptions on GetXamlType in XamlTypeInfo.g.cs
My App.xaml.cs
sealed partial class App : BootStrapper
{
public App() { InitializeComponent(); }
public override async Task OnStartAsync(StartKind startKind, IActivatedEventArgs args)
{
NavigationService.Navigate(typeof(ShellView))
await Task.CompletedTask;
}
}
My App.xaml
<Style x:Key="TitleStyle" TargetType="TextBlock">
<Setter Property="Foreground" Value="{StaticResource TextTitleForeground}"/>
<Setter Property="FontSize" Value="26"/>
<Setter Property="TextWrapping" Value="Wrap"/>
<Setter Property="FontWeight" Value="Medium"/>
</Style>
<Style x:Key="SubTitleStyle" TargetType="TextBlock" BasedOn="{StaticResource TitleStyle}">
<Setter Property="Foreground" Value="{StaticResource TextForeground}"/>
<Setter Property="FontSize" Value="20"/>
</Style>
Exception info:
Error HRESULT E_FAIL has been returned from a call to a COM component.
at System.Runtime.InteropServices.WindowsRuntime.IIterator`1.MoveNext()
at System.Runtime.InteropServices.WindowsRuntime.IteratorToEnumeratorAdapter`1.MoveNext()
at System.Linq.Enumerable.WhereEnumerableIterator`1.MoveNext()
at Template10.Common.BootStrapper.<InitializeFrameAsync>d__77.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Template10.Common.BootStrapper.<InternalLaunchAsync>d__53.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.AsyncMethodBuilderCore.<>c.<ThrowAsync>b__6_0(Object state)
at System.Threading.WinRTSynchronizationContext.Invoker.InvokeCore()

This was deeply confounding to me. I was easily able to reproduce this. And, then, I was easily able to reproduce this without a reference to Template 10. The offending code is this block in T10:
// title
foreach (var resource in Application.Current.Resources
.Where(x => x.Key.Equals(typeof(Controls.CustomTitleBar))))
{
var control = new Controls.CustomTitleBar();
control.Style = resource.Value as Style;
}
You could simplify it to this:
var a = Application.Current.Resources.ToArray();
Placed in the OnLaunched of any app's Application. Boom. The error itself comes when we are attempting to access the resource collection but only when a BasedOn style has been added to the resources.
After sitting down with the platform team to try and vindicate Template 10, everyone around the table started scratching their heads. And, that's when I realized #dachibox, you have discovered a genuine bug in the XAML platform.
Here's the only current workaround until we update Template 10.
<Page.Resources>
<ResourceDictionary Source="..\Styles\Custom.xaml" />
</Page.Resources>
What I mean is, you do the work in the page instead of in App. So, how will we fix Template 10 without the XAML platform getting fixed? Take a look at this wonky code we will be using in Bootstrapper:
int count = Application.Current.Resources.Count;
foreach (var resource in Application.Current.Resources)
{
var k = resource.Key;
if (k == typeof(Controls.CustomTitleBar))
{
var s = resource.Value as Style;
var t = new Controls.CustomTitleBar();
t.Style = s;
}
count--;
if (count == 0) break;
}
The error, at least, is in the iterator's count property which seems to increment instead of decrement as you iterate through it. Crazy huh? Turns out, this iteration path is not a common use case. But, that does not matter now, we've raised the flag thanks to your question here.
I'll update Template 10 with the fix sometime this week.
Best of luck, Jerry

Related

How to apply styles to all inner elements in MAUI on state change

This is basically simple UI that would conditionally render a label with text Triggered - plain in case the ShowContent property of binding is True. (Overly simplified example here but it works and I can see the label toggles).
<Grid>
<Button Click="ChangeState"/>
<ContentView>
<ContentView.Style>
<Style TargetType="ContentView">
<Style.Triggers>
<DataTrigger Binding="{Binding ShowContent}" Value="True" TargetType="ContentView">
<Setter Property="ControlTemplate">
<Setter.Value>
<ControlTemplate>
<Label>Triggered - Plain</Label>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentView.Style>
</ContentView>
</Grid>
There is also a button that would toggle the state of the Grid:
public void ChangeState(object sender, EventArgs e){
this.state = !this.state; // toggle
if(this.state){
VisualStateManager.GoToState(this, VisualStateManager.CommonStates.Selected);
} else {
VisualStateManager.GoToState(this, VisualStateManager.CommonStates.Normal);
}
}
Problem
I am not sure how to apply a different style to the label based on selected state.
If you use VisualStateManager, you need to have a name on the Label. I want to apply selected style in a blanket way on all inner labels.
Also, if we target the label with a name, when the Label is not on the UI (due to state ShowContent being False), GoToState fails with null exception as it cannot find the label.
The best solution seems to be using CSS but that does not support defined colors and dynamic resources (AFAIK).
Any idea what to do?
Update: one possible solution is to apply the state change to all inner elements:
private IList<T> FindAllChildren<T>()
where T : IVisualTreeElement
{
return this.GetVisualTreeDescendants()
.Where(e => e is T)
.Cast<T>()
.ToList();
}
private void ApplyState(string state)
{
VisualStateManager.GoToState(this, state);
FindAllChildren<VisualElement>().ForEach(e => VisualStateManager.GoToState(e, state));
}
public void ChangeState(object sender, EventArgs e){
this.state = !this.state; // toggle
if(this.state){
ApplyState(VisualStateManager.CommonStates.Selected);
} else {
ApplyState(VisualStateManager.CommonStates.Normal);
}
}
You still need to create VisualStateGroup styling for the labels and give labels a specific style/class:
<label class="Selectable">...</label>
<Style class="Selectable" TargetType="Label">
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup Name="all">
<VisualState Name="Normal">
<VisualState.Setters>
<Setter Property="Label.TextColor" Value="{DynamicResource Normal_Color}"/>
</VisualState.Setters>
</VisualState>
<VisualState Name="Selected">
<VisualState.Setters>
<Setter Property="Label.TextColor" Value="{StaticResource Selected_Color}"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
I recommend that you work around this. Too many bugs, and too different behavior on the different platforms for containers.
Fixing the visual state is one thing. Then you need to fix "IsEnabled" for child problem. After that if you change the visibility, you will notice that on IOS it is doing one thing, on android - another. (You will start losing this visual state from time to time). At some point you will start looking for ways to force the page to redraw itself.
My advice is, for now, give up on this idea. Until those problems are solved. Wasted too many hours trying to make this work for all platforms.
(Some of the issues are 6+ months old, and they keep pushing them to backlog.)
This is me, asking the same thing, a month ago: Pass the VisualState of CollectionView Item VisualElement to its child VisualElements
Edit: So, what work arounds I use.
Besides styles, visual states, data triggers?
ControlTemplates and Messages between ViewModel <-> View.
Control templates are reusable pieces of user interface, and there isn't much you have to do. You can make all VisualElements bind to the same thing, using TemplatedParent as BindingContext of the container.
Messages I use for some sorts of animations (And other special requests). You can in the ViewModel generate a message, that will be handled (or not) by the View. You have very good control over your View, but you do not break MVVM by coupling them.
A Warning: Every work around is parasitic code (you do something the wrong way, because someone else has been doing his job the wrong way). That code sooner or later will have to be deleted/replaced. Mark it with TODO, because it may take huge part of your app, and later it will be hard to find out all usage places. For now test on IOS. It takes much less work to make it work on IOS, then fix Android, than the other way around.

Xamarin Forms FontSize by Platform

There is a known issue with UWP Xamarin Apps in that the size that fonts render in for Windows Mobile (not sure about Desktop Windows 10) are MUCH larger than they render on the other two platforms iOS and Android. The fix I have found a for this is to put a numeric font size with a decimal point in it (for example 24.1 for Large font size) in UWP apps. This works great. What I would like not is to define a static resource in my App.xaml that will work for all platforms. What I tried, that obviously does not work, is the following:
<OnPlatform x:Key="CustLarge" x:TypeArguments="x:Double">
<On Platform="iOS|Android">Large</On>
<On Platform="UWP">24.1</On>
</OnPlatform>
The theory is that in my XAML I would simply code all my large font sizes as "CustLarge" like this:
FontSize = "{StaticResource CustLarge}" />
Any ideas how I can accomplish this without doing on OnPlatform for every FontSize in my app? Any help would be appreciated.
UPDATE: Below are screen shots of what I am talking about. The iOS emulator was for the iPhone 6 4.7" wide. The Windows 10 emulator was a the 10.0.15254.9 5" phone.
You can see the Map All text is way bigger than it should be. (I am doing a relative comparison to the text in the segment control to the right of the stand alone buttons.) In both cases the fontsize is set to MICRO.
So back to my question - does anyone know how to do this?
I was able to find a workaround by creating a custom ResourceDictionnary, and adding FontSizes in the constructor.
public class SResourceDictionnary : ResourceDictionary
{
public SResourceDictionnary()
{
Add("Micro", new OnPlatform<double>
{
Default = Device.GetNamedSize(NamedSize.Micro, typeof(Label)),
Platforms = { new On
{
Value = 12d,
Platform = new List<string>{"UWP"}
}
}
});
Add("Small", new OnPlatform<double>
{
Default = Device.GetNamedSize(NamedSize.Small, typeof(Label)),
Platforms = { new On
{
Value = 14d,
Platform = new List<string>{"UWP"}
}
}
});
Add("Medium", new OnPlatform<double>
{
Default = Device.GetNamedSize(NamedSize.Medium, typeof(Label)),
Platforms = { new On
{
Value = 18d,
Platform = new List<string>{"UWP"}
}
}
});
Add("Large", new OnPlatform<double>
{
Default = Device.GetNamedSize(NamedSize.Large, typeof(Label)),
Platforms = { new On
{
Value = 20d,
Platform = new List<string>{"UWP"}
}
}
});
}
Then I merged the dictionary in my App.xaml like this
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<helpers:SResourceDictionnary></helpers:SResourceDictionnary>
</ResourceDictionary.MergedDictionaries>
<Style TargetType="Label">
<Setter Property="FontSize" Value="{StaticResource Small}" ></Setter>
</Style>
</ResourceDictionary>
</Application.Resources>
It allowed me to use FontSize as StaticResource.
The only disadvantage is that you lose intellisense in Xaml

xaml VisualState set Grid.Row/Column of element

I have a Grid Layout with 7 elements in row. I want to move last 4 elements from first row to second if windowScreenWidth less than X. I already added a group and states.
If i use <Setter Target="el4.Grid.Row" Value="1"/> or <Setter Target="el4" Property="Grid.Row" Value="1"/> xaml throw exception.
Are there any way to make what i want?
The right XAML code is: Target="el4.(Grid.Row)" Value="1"/>
A bit late, but might help other people. Because the solution given wasn't the best (using if instead of style and states).
Found some workaround:
void WindowSizeChanged(object sender, SizeChangedEventArgs e)
{
double width = e.NewSize.Width;
if(width < 641)
{
el4.SetValue(Grid.RowProperty, 1);
el4.SetValue(Grid.ColumnProperty, 0);
}
else
{
el4.SetValue(Grid.RowProperty, 0);
el4.SetValue(Grid.ColumnProperty, 3);
}
}
public MainPage()
{
SizeChanged += WindowSizeChanged;
this.InitializeComponent();
}

Sharing styles between several GridView controls

I need to style several GridView throughout my application with the same visual styles. This style includes customizing the ItemsPanel property as well as the GroupStyle property.
My problem is that the GroupStyle property of GridView is not a dependency property. So the code I would have liked to write (see below) does not work.
Do you know a clean way to share a style (including GroupStyle) between several GridViews?
The only thing I can think of is using a GroupStyleSelector but it's kind of stupid since there is no selection to make: it's always the same GroupStyle that's being used. Moreover, I suspect it wouldn't be reflected at design time in VS & Blend.
The code I would love to use:
<GridView
ItemsSource="..."
ItemTemplate="..."
Style="{StaticResource MainMenuStyle}"/>
<Style TargetType="GridView" x:Key="MainMenuStyle">
<Setter Property="ItemsPanel">
<Setter.Value>
...
</Setter.Value>
</Setter>
<Setter Property="GroupStyle">
<Setter.Value>
<GroupStyle>
...
</GroupStyle>
</Setter.Value>
</Setter>
</Style>
I've got a magical happy solution.
You can create a custom Attached Property that you set in the Style, and upon setting it internally sets the GroupStyle property on the GridView.
Attached Property:
// Workaround for lack of generics in XAML
public class GroupStyleCollection : Collection<GroupStyle>
{
}
public class GroupStyleHelper
{
public static ICollection<GroupStyle> GetGroupStyle(ItemsControl obj)
{
return (ICollection<GroupStyle>)obj.GetValue(GroupStyleProperty);
}
public static void SetGroupStyle(ItemsControl obj, ICollection<GroupStyle> value)
{
obj.SetValue(GroupStyleProperty, value);
}
public static readonly DependencyProperty GroupStyleProperty =
DependencyProperty.RegisterAttached(
"GroupStyle",
typeof(ICollection<GroupStyle>),
typeof(GroupStyleHelper),
new PropertyMetadata(null, OnGroupStyleChanged));
private static void OnGroupStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ItemsControl itemsControl = d as ItemsControl;
if (itemsControl == null)
return;
RefreshGroupStyle(itemsControl, GetGroupStyle(itemsControl));
}
private static void RefreshGroupStyle(ItemsControl itemsControl, IEnumerable<GroupStyle> groupStyle)
{
itemsControl.GroupStyle.Clear();
if (groupStyle == null)
return;
foreach (var item in groupStyle)
{
itemsControl.GroupStyle.Add(item);
}
}
}
XAML Style:
<Style TargetType="ItemsControl">
<Setter Property="GroupStyleTest:GroupStyleHelper.GroupStyle">
<Setter.Value>
<GroupStyleTest:GroupStyleCollection>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock FontWeight="Bold" FontSize="15" Text="{Binding Path=Name}" Foreground="HotPink"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</GroupStyleTest:GroupStyleCollection>
</Setter.Value>
</Setter>
</Style>
Disclaimer: I'm testing this in WPF rather than WinRT but it should work the same, as far as I can tell. That's also why I'm using an ItemsControl rather than GridView, but the property is ItemsControl.GroupStyle anyway.
I've a solution and that will definitely work as per your question, but though you should decide whether to use that in your case or not.
If you have to make same style of a control in all over the project, then you should make one common folder and in that folder
create one "Custom User Control" and apply all of your style and
customize it however you want.
After that when you need to apply that same kind of style on same control (any grid control) then simply add that customized user
control instead of predefined control
By doing this you'll also achieve MVC architecture and modularity.
I'm developing Windows 8 Metro app in C# with XAML, and in that whenever i wanted this approach then i always use this solution and it always works...
to create custom user control, you should use visual studio & in that right click on project -> add -> new item -> User Control
(Sorry if you couldn't find your solution here, but i think this might help...)

DataTrigger that doesn't undo itself when condition no longer met

Is there a way to do something like the following?
<Style TargetType="{x:Type: TreeViewItem}">
<Style.Triggers>
<DataTrigger Binding="~Complex Binding~" Value="True" DoNotUnset="True">
<Setter Property="IsExpanded" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
What I basically would like is this to be a "No Undo DataTrigger" if you will. When the Value is no longer "True" I don't want it to set "IsExpanded" back to its previous value.
Here is my attempt to do this using enter actions but this also has problems.
<Style TargetType="{x:Type: TreeViewItem}">
<Style.Triggers>
<DataTrigger Binding="~Complex Binding~" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<BeginStoryboard.Storyboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="(IsExpanded)" Duration="00:00:01" FillBehavior="Stop">
<BooleanKeyFrameCollection>
<DiscreteBooleanKeyFrame KeyTime="00:00:00" Value="True"/>
</BooleanKeyFrameCollection>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard.Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
First this is insanely verbose but secondly this only works for the duration of the BooleanAnimationUsingKeyFrames. If I change the FillBehavior to "HoldEnd" then it looks correct but now the user can no longer un-expand the treeviewitem by clicks (though oddly enough they still can by using the keyboard).
For those who are wondering: yes I'm trying to expand all items in a tree view by binding because I don't want to build a recursive ItemsGenerator.GetItemContainerFromIndex(i) loop. I'd still like to use a similar "No Undo Datatigger" in other areas of my code.
I would bind the ~Complex Binding~ to a bool in the ViewModel which once set to true is always true.
private bool _onceTrueAlwaysTrue = false;
public bool OnceTrueAlwaysTrue
{
get
{
return _onceTrueAlwaysTrue;
}
set
{
if(value)
{
_onceTrueAlwaysTrue = true;
OnPropertyChanged("OnceTrueAlwaysTrue");
}
}
}
then bind this property to the IsEnabled and you should be fine. If you want to reset it simply make a reset method that sets _onceTrueAlwaysTrue = false;
As for the NoUndo datatrigger, as far as I know there is no such thing. You are gonna have to do some kind of work around every time.
The properties changed by triggers are automatically reset to their previous value when the triggered condition is no longer satisfied. Triggers are optimized for transient states which are expected to change and return to original state, such as IsPressed on Button and IsSelected on ListBoxItem.