dotnet / maui

.NET MAUI is the .NET Multi-platform App UI, a framework for building native device applications spanning mobile, tablet, and desktop.
https://dot.net/maui
MIT License
21.98k stars 1.71k forks source link

[iOS] DataTrigger fires on first Entry for no apparent reason #20927

Open MAUIoxo opened 6 months ago

MAUIoxo commented 6 months ago

Description

I have a DataTrigger for an Entry which is defined inside my ContentView.Resources. It should and change the color of the Entry when the Min value is greater than the corresponding Max value. This works so far as can be seen in the very beginning of the attached GIF.

The DataTriggeris defined as follows:

<ContentView.Resources>
    <ResourceDictionary>
        ...
        <Style x:Key="EntryStyle" TargetType="Entry">
            <Setter Property="TextColor" Value="Black" />
            <Setter Property="Keyboard" Value="Numeric" />
            <Setter Property="VerticalOptions" Value="Center" />
            <Setter Property="HorizontalTextAlignment" Value="Center" />
            <Setter Property="Margin">
                <Setter.Value>
                    <OnPlatform x:TypeArguments="Thickness">
                        <On Platform="iOS" Value="0, 0, 5, 0" />
                        <On Platform="Android" Value="0, -5, 0, -3" />
                    </OnPlatform>
                </Setter.Value>
            </Setter>
            <Style.Triggers>
                <DataTrigger TargetType="Entry" Binding="{Binding IsValid}" Value="False">
                    <Setter Property="TextColor" Value="{StaticResource Orange}" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
        ...
    </ResourceDictionary>
</ContentView.Resources>

<SwipeView>
    <Border Style="{StaticResource BorderStyle}">

        <!-- Details of a selected Store Item Element -->
        <StackLayout>

            <Grid ColumnDefinitions="*, 60, 60, 70" Margin="0, 10, 0, 10">

                ...

                <!-- Min Profit -->
                <HorizontalStackLayout Grid.Row="0" Grid.Column="1" Margin="10, 0, 10, 0">
                    <Entry x:Name="MinEntry" Text="{Binding Min, Mode=TwoWay}" Style="{StaticResource EntryStyle}" TextChanged="Entry_TextChanged" Focused="Entry_Focused" />
                    <Label Text="$" VerticalOptions="Center" />
                </HorizontalStackLayout>

                <!-- Max Profit -->
                <HorizontalStackLayout Grid.Row="0" Grid.Column="2" Margin="10, 0, 0, 0">
                    <Entry x:Name="MaxEntry" Text="{Binding Max, Mode=TwoWay}" Style="{StaticResource EntryStyle}" TextChanged="Entry_TextChanged" Focused="Entry_Focused" />
                    <Label Text="$" VerticalOptions="Center" />
                </HorizontalStackLayout>

                <!-- Optimal Profit -->
                <HorizontalStackLayout Grid.Row="0" Grid.Column="3" VerticalOptions="Center" HorizontalOptions="End">
                    <Label Text="{Binding OptimalProfit, Mode=OneWay}" VerticalOptions="Center"/>
                    <Label Text="$" VerticalOptions="Center" Margin="3"/>
                </HorizontalStackLayout>
            </Grid>

            ...

        </StackLayout>
    </Border>
</SwipeView>

The ContentView with the Entrys is defined inside a DataTemplate:

<DataTemplate x:Key="VerticalItemTemplate" x:DataType="{x:Type databaseModels:StoreSelection}">
    <sho:DraggableViewCell>
        <views:StoreSelectionItemView />
    </sho:DraggableViewCell>
</DataTemplate>

There is a Model class StoreSelection which is bound to this StoreSelectionItemView and contains the Entry for the Min value and the Max value.

The DataTrigger should trigger during input of values and underneath set an IsValid property when these values change so that it can calculate a bool property value IsValid which is true when Max >= Min. The data model class is derived from ObservableObject:

#region Min Profit

private int _min = 0;

[Column(Order = 6)]
public int Min
{
    get => _min;
    set
    {
        SetProperty(ref _min, value);
        OnPropertyChanged(nameof(IsValid));
    }
}

#endregion

#region Max Profit

private int _max = 0;

[Column(Order = 7)]
public int Max
{
    get => _max;
    set
    {
        SetProperty(ref _max, value);
        OnPropertyChanged(nameof(IsValid));
    }
}

#endregion

...

#endregion        

#region IsValid

[NotMapped]
[Column(Order = 9)]
public bool IsValid { get => Max >= Min; }

#endregion



Issue: The issue is, as can be seen in the attached animation below, that after the first items were selected and the DataTrigger fires correctly and we deselect elements and add new ones, the DataTrigger fires on the first Entry without apparent reason and even if Min == Max it fires. This can be seen on iOS only:

DataTriggerFiresOnFirstEntry


Android:

DataTriggerFiresOnFirstEntryAndroid

Steps to Reproduce

Watch the animations

  1. Click on Dotnet-Bot icon to open the BottomSheet menu
  2. Select first and second item to add them. That's the View StoreSelectionItemView.xaml defined via DataTemplate in TabView1.xaml
  3. See that initially the DataTrigger of the first Entry does not trigger, which is correct as the Min value is 0 and the Max value is 0
  4. Type in some values and see that when Min > Max the DataTrigger fires correctly and changes the TextColor of the Entry
  5. Click on Dotnet-Bot icon to open the BottomSheet again deselect the first item
  6. Click on Dotnet-Bot icon to open the BottomSheet again and select the first item again
  7. See that the DataTrigger of the first Entry fires and color is changed to the "Error"-Color even if the value is "0"
  8. Try on iOS and Android and see that it behaves correctly just on Android

Link to public reproduction project repository

DataTriggerCorrupted_20927

Version with bug

8.0.6-nightly.9863 up to 8.0.10-nightly.10061

Is this a regression from previous behavior?

Yes, this used to work in .NET MAUI

Last version that worked well

7.0.101

Affected platforms

iOS

Affected platform versions

iOS 17.2

Did you find any workaround?

Well, no working workaround in the end.

A workaround would be to use ObservableRangeCollection.Add(..., NotifyCollectionChangedAction.Reset) or use new ObservableRangeCollection() and so on. But all kinds of actions in which a NotifyCollectionChangedAction.Reset is involved result in rising memory consumption and finally crash my App on my iPhone without hitting any of the try-catch-Blocks wrapped around.

I described this in detail in #21015

But, this would fix the issue and don't show the effect of this issue.

Relevant log output

No response

Zhanglirong-Winnie commented 6 months ago

Verified this issue with Visual Studio 17.10.0 Preview 1. Can repro on iOS platform with sample project. DataTriggerCorrupted_20927