Silverlight TwoWay-bound ComboBox setting values to NULL - data-binding

Before I explain my issue, consider the following object:
Character.cs
-> AnimationControlSettings.cs
.. -> UpControlType (string)
.. -> AvailableControlTypes (List<string>)
The relevant properties in my ViewModel:
Character SelectedCharacter
ObservableCollection<Character> Characters
I have a simple View where you select a character using a ComboBox. The ComboBox's SelectedItem is TwoWay-bound to the ViewModel's SelectedCharacter property. There are other textboxes/checkboxes (also two-way bound to various properties of SelectedCharacter) that maintain their values properly as I switch between Characters.
The problem exists in the ComboBox bound to the UpControlType property:
<ComboBox x:Name="lstUpControlTypes"
ItemsSource="{Binding Path=SelectedCharacter.AnimationControlSettings.AvailableControlTypes}"
SelectedItem="{Binding Path=SelectedCharacter.AnimationControlSettings.UpControlType, Mode=TwoWay}">
</ComboBox>
Initial values are displayed in this ComboBox correctly but as soon as I switch from CharacterA to CharacterB, CharacterA's UpControl property is set to NULL and I have no idea why.
Here is a barebones repro of this exact issue (VS2010, SL4):
http://www.checksumlabs.com/source/TwoWayBindingWorkshop.zip
If you run that solution you'll see that the Name property persists as you switch Characters but the UpControlType value's getting set to NULL.
Am I missing something obvious here?

You are binding the items source of the third combo box to a property inside the SelectedCharacter, like this:
ItemsSource="{Binding SelectedCharacter.AnimationControlSettings.AvailableControlTypes}"
This means that when SelectedCharacter changes the items source for that combo box will be reset and this activates the two way binding you set in the SelectedItem of the same combo box, setting your property to null:
SelectedItem="{Binding SelectedCharacter.AnimationControlSettings.UpControlType, Mode=TwoWay}"
I was able to fix the issue by moving the property AvailableControlTypes to the CharacterViewModel class, which means that when you change the character, the available types remain the same. If this is acceptable in your situation, it will fix your problem:
<ComboBox x:Name="lstUpControlTypes"
ItemsSource="{Binding AvailableControlTypes}"
SelectedItem="{Binding SelectedCharacter.AnimationControlSettings.UpControlType, Mode=TwoWay}" />

Related

CollectionView template calls converter incorrectly

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.)

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"

Xamarin Forms Picker SelectedItem Binding

The Xamarin Forms doc Xamarin.Forms.Picker.SelectedItem says there is a public property SelectedItem for Picker. However, I get an error when I try to bind to it. The picker is not very useful if you have to manually handle the SelectedIndex property.
Tony
No need to manually handle the SelectedIndex. You can use the Picker's SelectedItem property. Just make sure your types are the same. For example, if your ItemsSource is bound to a property:
BookTitles List<string> { get; set; }
your SelectedItem has to be something like:
SelectedBookTitle string { get; set; }
Make sure to set the SelectedBookTitle value to show a title when the page is first shown.
Do not forget to set Mode to TwoWay on the SelectedItem Binding.
for example:
<Picker ItemsSource="{Binding BookTitles}" SelectedItem="{Binding
SelectedBookTitle, Mode=TwoWay}" />
This will ensure the title is shown when the page is first displayed,
and keeps the value of SelectedBookTitle equal on the Page and codebehind/viewmodel.
No need to use behaviours in this example.
You can add this feature relatively easy, even with older versions of Forms pre 2.3.4 which supports it out of the box. Just create a custom behaviour to bind the picker items. You can implement your own version or use an existing library, like the Xamarin University Infrastructure Library which is available as source and as a Nuget
The detailed documentation shows how to use it:
<Picker ...>
<Picker.Behaviors>
<inf:PickerBindBehavior Items="{Binding Colors}"
SelectedItem="{Binding FavoriteColor}" />
</Picker.Behaviors>
</Picker>
The approach of the behaviour is to expose a bindable property (the items) and use an observable collection. Whenever that changes, the behaviour updates the items of the picker.

How to bind the ItemSource inside the DataGrid to an element outside the DataContext, eg. ViewModel in Silverlight 4

I have a DataGrid with some text columns and a button. I want to bind button to a command on the ViewModel. Since, Columns are inside the context of the ItemSource, i want to change the DataContext for the button to something outside the DataGrid(to a view model, to access the Command) or else Silverlight is not able to find the binding expression for that command on the ItemSource context.
Here is what i am doing but i am unsuccessful in doing so. I am not sure where i am making a mistake
<DataGrid >
...
<Button>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding Path=DataContext.CommandToCall, ElementName=DataGridName}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
...
</DataGrid>
I do not get any Binding error on doing this but i cannot invoke the command inside my ViewModel. Please note, it is for silverlight and not WPF.
Thanks
I had this same problem recently. I was using a Telerik RadGridView, and I found a workaround on their support forum. Maybe you can do something similar.
Here's the question, and here's the workaround.

WPF - Binding a variable in an already bound ListBox?

I really don't know how to title this question, but I need some help with binding to a ListBox.
I have an object, that contains (among other information) 2 properties that need to be bound in one ListBox. One of these is an ObservableCollection of objects, called Layers, and the other property holds an enum value of either Point, Line or Polygon, called SpatialType. These are to act as a legend to a map application. I have bound Layers to a ListBox, no problem, but inside the ListBox.ItemTemplate, I need to bind the single variable SpatialType to every Item in the ListBox. The problem I'm running into is that when I try to bind while inside the ListBox, the only variables I have access to are the properties of each Layer and I can't access any properties of the original bound class that holds the Layers (and the needed SpatialType property).
What can I do to get that piece of information bound inside the ItemTemplate without messing up a good MVVM architecture?
You can in your view have a ObjectDataProvider that is the basis for your datacontext and have the ObjectInstance point to your viewmodel. Then you can bind from the ItemTemplate like so:
<UserControl.Resources>
<ObjectDataProvider x:Key="Data"/>
<DataTemplate x:Key="Template">
<TextBlock Text="{Binding SpatialType,Source={StaticResource Data}}/>
</DataTemplate>
</UserControl.Resources>
<Grid DataContext={Binding Source={StaticResource Data}}>
<ListBox ItemsSource="{Binding Layers}" ItemTemplate="{StaticResource Template}"/>
</Grid>
Hope this helps.
What I ended up doing was using FindAncestor to get the DataContext of a higher "tier" in binding:
Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBox}}, Path=DataContext.LayerSymbolization.SpatialType, Mode=TwoWay}"

Resources