CollectionView template calls converter incorrectly - xamarin.forms

Xamarin.Forms 5.0.0.2012, Visual Studio 2019 for Mac 8.10.16
I am encountering a problem (on both iOS and Android, on both emulators and physical devices) trying to call a Converter in the XAML describing a new screen in our app. This is the markup:
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:internal="clr-namespace:Views.Shared"
x:Class="Views.EditPage">
<ContentPage.Resources>
<internal:PictureNameToImageSourceConverter x:Key="PictureNameToImageSourceConverter" />
</ContentPage.Resources>
...
<CollectionView
x:Name="picturesView"
ItemsLayout="HorizontalList"
HeightRequest="90">
<CollectionView.ItemTemplate>
<DataTemplate>
<Frame HasShadow="False" Padding="5">
<Image
HeightRequest="80"
Source="{Binding Path=., Converter={StaticResource PictureNameToImageSourceConverter}}" />
</Frame>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
The CollectionView.ItemsSource value is set in code to a List<string> instance. When the List has no items, the screen displays correctly. When it does have items the app crashes as the screen appears. If I launch the app attached to the VS debugger, the screen freezes before the crash, and I never get any information.
This converter is well tested and is used several other places in the app with no problem. When I replace the Image with <Label Text="{Binding Path=.}"/> the text items are displayed as expected, so it doesn't look like a binding or Path syntax error.
Is there something I'm not seeing or not aware of in the markup that's causing this? Or can anyone suggest further debugging I haven't thought of?
UPDATE
A breakpoint on the very first line of the converter was never reached.
EDIT in response to comments:
From the EditPage codebehind:
public partial class EditPage : ContentPage
{
internal List<string> PictureNames;
...
protected override void OnAppearing( )
{
base.OnAppearing();
picturesView.ItemsSource = PictureNames;
}
The PictureNames property is actually set by a different Page to which one navigates from the EditPage:
private void saveSelection_Click(object sender, System.EventArgs args)
{
creator.PictureNames = new List<string>();
foreach (SelectableItem<Picture> item in pictureItems)
{
if (item.IsSelected)
{
creator.PictureNames.Add(item.Item.PictureName);
}
}
Navigation.PopAsync();
}
pictureItems is a List acting as a ListView.ItemsSource on that screen, where pictures are selected or unselected.
UPDATE
After much setting of breakpoints, I've determined that the line picturesView.ItemsSource = PictureNames; is where the crash happens. It seems odd that it only happens when the template is showing an Image, but not a Label, seeing that the converter is never actually called.
UPDATE
The trick of adding the delay did get me to the breakpoint. And what I found is more puzzling than ever: The value parameter being passed to the Convert method of our converter is null. This is the case whether coming back from the picture selection screen, or if I set the bound list in response to a Button rather than in OnAppearing, or if I just set it right in the page constructor.
In addition, when setting a breakpoint on the crashing line, when the display element in the template is a Label everything is as expected, but when the display element is an Image the debugger freezes when trying to look at those values. The problem is apparently something about the fact of calling a converter in this precise situation.
I tested that by adding a different converter to the template:
<CollectionView.ItemTemplate>
<DataTemplate>
<Frame HasShadow="False" Padding="5" HeightRequest="{Binding Path=., Converter={StaticResource PictureNameToHeightConverter}}">
<Label Text="{Binding Path=.}" />
</Frame>
</DataTemplate>
</CollectionView.ItemTemplate>
Exactly the same result: The value parameter passed to the Convert method is null even though the same bound value in the same template instance is displayed in the Label. If I set a breakpoint on the line assigning the ItemsSource property as before, the debugger freezes.
UPDATE
Finally beginning to suspect that I'm triggering some corner case bug in the framework, I replaced the CollectionView with a CarouselView, and it works correctly:
<CarouselView
x:Name="picturesView"
HeightRequest="90">
<CarouselView.ItemTemplate>
<DataTemplate>
<Frame HasShadow="False" Padding="5">
<Image
HeightRequest="80"
Source="{Binding Path=., Converter={StaticResource PictureNameToImageSourceConverter}}" />
</Frame>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
I'm adding this as an update to the question rather than as an answer because I still don't actually know the explanation.

I've seen some odd behavior when updating page details in OnAppearing.
Hard to put a finger on exactly when there will be a problem, but the fact that you navigated to another page, so this is OnAppearing during the "back" from that other page is probably a factor.
Try this:
protected override void OnAppearing( )
{
base.OnAppearing();
Device.BeginInvokeOnMainThread( async () => {
// Let the page appear on screen, before setting ItemsSource.
await System.Threading.Tasks.Task.Delay(200);
picturesView.ItemsSource = PictureNames;
});
}
At minimum, this should allow Xamarin to reach the breakpoint in the converter.
——————-
UPDATE
I don’t see any obvious flaw in the code you’ve posted.
You’ve narrowed it down to the line it crashes on.
Yet a breakpoint at start of converter is never reached.
As you say, this is a puzzling combination of facts.
Here is a test to do:
Put breakpoint on the line that crashes.
Copy the values in the list, to a text editor.
Add a button on the current page, that when pressed, fills the list with those same strings, hardcoded as a literal, then sets the ItemSource.
Start over, but this time press the button - Does this work or crash?
That is, remove all the complexity of going to another page, querying values, returning to this page.
I bet this will work. Then you’d have the best situation for debugging: a case that works vs. one that doesn’t.
After that, its “divide and conquer”. Start making the working one more like the broken one, and/or vice versa, until the culprit is identified.

You could try to pass data with the ways below when you do the navigation.
One way is to set the BindingContext to the page which you want to navigate to.
Use the Navigation.PushAsync to navigate the page and reset the BindingContext before the page navigated. You could check the thread i done before for more details. Xamarin Public Class data how to acess it properly
Or you could try to binding to a path property using MVVM.
<Image HeightRequest="80" Source="{Binding path, Converter={StaticResource PictureNameToImageSourceConverter}}" />
Another way is to pass the data to the page which you want to navigate through a Page Constructor.
For more details of this way, check the MS docs. https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/navigation/hierarchical#passing-data-through-a-page-constructor

While it would be interesting to know why the CollectionView binding isn't behaving as expected in this specific situation, the number of hours spent trying to figure it out has definitely passed the point of diminishing returns, so I just added the images the old-fashioned way. I replaced the CollectionView with a StackLayout:
<StackLayout x:Name="picturesLayout" Orientation="Horizontal" />
And then just added framed images "by hand" in the OnAppearing method:
picturesLayout.Children.Clear();
foreach (string pictureName in PictureNames)
{
picturesLayout.Children.Add(new Frame { Padding = 5, HasShadow = false, Content = new Image { Source = ImageManager.ReadFromDisk(pictureName), HeightRequest = 80 } });
}
(ImageManager handles chores like keeping track of the full path to the appropriate directory.)

Related

How tell command (on page) which control was pressed?

i have a big knot in my brain.
The current Situation is the following:
in my xamarin Forems App i have on some Pages a UserControl (House) witch Contains multiple Controls (Floor) and this has also multiple Controls (Rooms) in it.
This is all working perfectly and i have coll control for each room over a HouseControler Class
On my RoomControl i have:
<Grid.GestureRecognizers>
<TapGestureRecognizer Command="{Binding Source={x:Reference Root}, Path=RoomTappedCommand}" />
</Grid.GestureRecognizers>
this is totally perfekt. and i got my command perfectly fired in the Breakpoint.
But what i want to acheve is to open nut just an alert but a custom Popup with a Navigation from the current Page with the Informations of the Tapped Room.
So in need do pass somehow the informations from the RoomControl up to the Page. But i dont know how.
i would love to read some tipps.
Thank you Guys =)
you can always get a reference to the currently active page using App.Current.MainPage. To display an alert, for example
App.Current.MainPage.DisplayAlert(...);

How to wrap long messages using Editor in Xamarin Forms

I am using Editor control and wish to wrap long messages (Like Whatsapp).
I have explored few sites and got nothing . If this feature is not yet in Xamarin Forms , can I know any alternative for the same ?
My XAML
<Editor Grid.Row="0" Grid.Column="0" Text="{Binding TextMessage}"
TextColor="{StaticResource StartColor}" HorizontalOptions="FillAndExpand"
FontSize="14" TextChanged="Editor_TextChanged"
FontFamily="{StaticResource DefaultFont}" IsEnabled="True"/>
OUTPUT-
I would first suggest you to use AutoSize property of Editor, so if you want your editor to automatically resize depending on the text length, you should set it to TextChanges, otherwise set it to Disabled(this is default value).
Next thing, if you know that the Editor will be used for chat, you can also set Keyboard property to Chat:
<Editor Text="Enter text here" AutoSize="TextChanges" Keyboard="Chat" />
If I am not mistaken, The Editor control has a property for this called the AutoSize Property which takes the EditorAutoSizeOption enum as an input. Available options are TextChanges and Disabled.
<Editor AutoSize="TextChanges"...
If for some reason this does not work for you then you can use the solution in this forum discussion. by ionixjunior.
For those who are still facing issues, I have a case where the line does not wrap as long as there is no break line (Enter). Solution was to change parent layout from StackLayout to Grid. With grid, now it works.
If you want the height to change dynamically when you have more lines, you can use the above solution:
AutoSize="TextChanges"

Adding item to collection does not refresh activity designer

Am re-hosting my designer and have added a toolbox item which has a WorkflowItemsPresenter to render multiple child activity items. So my model item has a collection as:
Sequence _innerSequence = new Sequence();
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[Browsable(false)]
[Description("")]
public Collection<Activity> Activities { get { return _innerSequence.Activities; } }
and the binding is
<sap:WorkflowItemsPresenter Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" Items="{Binding Path=ModelItem.Activities}" VerticalAlignment="Center" HorizontalAlignment="Center" HintText="Drop activities here">
<sap:WorkflowItemsPresenter.SpacerTemplate>
<DataTemplate>
<Label HorizontalAlignment="Center" Height="21"/>
</DataTemplate>
</sap:WorkflowItemsPresenter.SpacerTemplate>
</sap:WorkflowItemsPresenter>
The binding works absolutely fine when the designer is loaded. Now i want to clear and add new items to this collection when some filter is updated on the designer. So in the set accessor of this filter property i call a method which does the following:
private void RefreshApplication()
{
Activities.Clear();
Activities.Add(new AddXYZApplication() { ApplicationName = "Test" });
}
Though the collection gets updated the designer does not refresh. I know there is a way of doing this by writing some designer code-behind (i.e. updating the collection via the model item tree), but I would ideally like to have NO code-behind and expect it work like any other WPF application.
Any help would be greatly appreciated as I have been trying to devise a way (using multi-binding, using eventing etc) from sometime now.
Cheerio,
V
Just to answer after my comment.
When you're editing through WorkflowDesigner you haven't an Activity per se. What you have is a ModelItem.
From the moment you load an activity to the designer, through WorkflowDesigner.Load(activity) you don't have an activity anymore, you just start editing a ModelItem.
You can access XAML through WorkflowDesigner.Text (after flush it) and do whatever you want with it. For example load it into an ActivityBuilder or WorkflowService but that is it.
Designers don't known, and never will, that activities have Collection<Activities>, Collection<Variable> or any other properties. They only know the ModelItem and the properties it has, period.
In resume: changes have to be made to ModelItem because it's what designers bind to.

In custom WorkflowViewElement, how to support WF4.5 Annotations?

I've created a a custom activity designer, but I wanted to have control over the 'header' part (that by default shows the ModelItem.DisplayName), and as far I know, the only way to achieve this is to derive the designer from WorkflowViewElement rather than ActivityDesigner. So, that seems to work and all is well. Except, I want to support the new Annotations feature of WF4.5.
Given that when you add an annotation to a workflow element on the design surface it adds an icon to the element you are annotating + the actual annotation element, it seems clear that any custom WorkflowViewElement has to have some extra stuff in it to support this behaviour. Can anyone help?
Thanks
As I thought, annotations are saved within the workflow definition as an attached property. Here's what an annotation looks like in the workflow's xaml:
<Sequence
xmlns:derp="http://schemas.microsoft.com/netfx/2010/xaml/activities/presentation"
derp:Annotation.AnnotationText="This is an annotation!">
See, just like any other attached property. Except it isn't. Its an attached workflow property, not an attached DependencyProperty. That means it works through the attached property service as well as the Annotation class. Getting and setting the annotation text on a ModelItem is trivial (and covered below).
Its actually not that hard to support annotations. As long as you don't mind your UI looking like crap. Here's a quick and dirty implementation.
In the UI, add some controls for holding and editing the annotation text
<sap:WorkflowViewElement
x:Class="AnnotationSupport.MyActivityDesigner"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sap="clr-namespace:System.Activities.Presentation;assembly=System.Activities.Presentation"
xmlns:ann="clr-namespace:System.Activities.Presentation.Annotations;assembly=System.Activities.Presentation"
xmlns:sapv="clr-namespace:System.Activities.Presentation.View;assembly=System.Activities.Presentation"
x:Name="root"
MinWidth="100"
MinHeight="100">
<Grid Background="red">
<Grid.RowDefinitions>
<RowDefinition
Height="auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Expander
IsExpanded="False">
<!-- HERE SHE BLOWS -->
<TextBox
Text="{Binding ModelItem.AnnotationText}" />
</Expander>
<TextBox
Grid.Row="1"
Text="{Binding ModelItem.Text}"
Margin="10" />
</Grid>
</sap:WorkflowViewElement>
Any time the Annotation text changes, the ModelItem's PropertyChanged event fires, just like any other property. And if you want to grab it from code, the simplest way is to cast the ModelItem to a dynamic:
private void SetAnnotationLol(string newValue)
{
if(ModelItem != null)
((dynamic)ModelItem).AnnotationText = newValue;
}
Now, if you want to create a nice UI like the Fx Activities have... well...
I'll leave it up to you to create a custom Adorner to handle display and editing of the annotation. Which actually isn't as hard as it first looks. If you haven't done one yet, here's your opportunity.

Entity graphy binding not working in deep level

Suppose I have entity graph like
People ->Student
then in xaml, I have following kind of binding(People is property of VM):
<TextBox Text="{Binding People.Name, Mode=TwoWay}" />
<TextBox Text="{Binding People.Student.StudentNo, Mode=TwoWay}" /> <!-- this bounding is not working -->
in VM, implementing IEditableObject. I have some code like:
public void BeginEdit()
{
((IEditableObject)this.People).BeginEdit();
((IEditableObject)this.People.Student).BeginEdit(); //this code not working
//....
}
When runing the app, all data bound to People is fine.
All data bound to Student is not working.
How to fix it?
I'd guess that the Student property is null on the client side. You need to add the [Include] attribute to the Student property on the server-side, so that it gets taken across to the client side by RIA services. You may also need to add an include for your server-side to retrieve it from the database, depending on how your data-access is written.

Resources