Skip to content

Commit

Permalink
Merge pull request #6371 from unoplatform/dev/xygu/20210630/selection…
Browse files Browse the repository at this point in the history
…-dp-event-ordering

fix(Selector): firing order of data-bound dp & event
  • Loading branch information
Xiaoy312 authored Aug 5, 2021
2 parents 678fd5b + d593602 commit 22e4300
Show file tree
Hide file tree
Showing 9 changed files with 412 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,43 @@ void AdvanceAutomation(string automationStep)
}
}

[Test]
[AutoRetry]
public void ListView_Selection_Events_Ordering()
{
Run("UITests.Windows_UI_Xaml_Controls.ListView.ListView_Selection_Events");
_app.WaitForElement("EventLogs");

var eventLogs = _app.Marked("EventLogs");
var setSelectIndexTo0Button = _app.Marked("SetSelectIndexTo0Button");
var clearLogsButton = _app.Marked("ClearLogsButton");

// selecting item 1 manually
clearLogsButton.Tap(); // clear events proc from setting initial data-context
_app.Tap("Item_1");
var logs = eventLogs.GetDependencyPropertyValue<string>("Text");
Assert.AreEqual(logs, GenerateItemSelectionLogs(1, "Item_1"));

// selecting item 0 programmatically
clearLogsButton.Tap(); // clear events from the step above
setSelectIndexTo0Button.Tap();
logs = eventLogs.GetDependencyPropertyValue<string>("Text");
Assert.AreEqual(logs, GenerateItemSelectionLogs(0, "Item_0"));

string OnPropertyChanged(string name, object value) => $"VM.PropertyChanged: [{name}]->{value}";
string OnSelectionChanged((string, string, int) lv, (string, string, int) vm) => string.Join("\n",
"LV.SelectionChanged: (Item|Value|Index): ",
$"\t- lv:({lv.Item1}|{lv.Item2}|{lv.Item3}), ",
$"\t- vm:({vm.Item1}|{vm.Item2}|{vm.Item3})"
);
string GenerateItemSelectionLogs(int index, string value) => string.Join("\n",
OnPropertyChanged("SelectedIndex", index),
OnPropertyChanged("SelectedItem", value),
OnPropertyChanged("SelectedValue", value),
OnSelectionChanged(lv: (value, value, index), vm: (value, value, index))
);
}

private void ClickCheckBoxAt(int i)
{
_app.Marked("CheckBox").AtIndex(i).Tap();
Expand Down
7 changes: 7 additions & 0 deletions src/SamplesApp/UITests.Shared/UITests.Shared.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -1433,6 +1433,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Controls\ListView\ListView_Selection_Events.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Controls\ListView\ListView_WithScrollViewer.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
Expand Down Expand Up @@ -5189,6 +5193,9 @@
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Controls\ImageTests\Image_UseTargetSize.xaml.cs">
<DependentUpon>Image_UseTargetSize.xaml</DependentUpon>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Controls\ListView\ListView_Selection_Events.xaml.cs">
<DependentUpon>ListView_Selection_Events.xaml</DependentUpon>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Controls\ListView\ListView_WithScrollViewer.xaml.cs">
<DependentUpon>ListView_WithScrollViewer.xaml</DependentUpon>
</Compile>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<Page x:Class="UITests.Windows_UI_Xaml_Controls.ListView.ListView_Selection_Events"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:UITests.Windows_UI_Xaml_Controls.ListView"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="1*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="1*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>

<TextBlock Grid.Row="0"
Text="Item for selection:" />
<ListView Grid.Row="1"
x:Name="SampleListView"
ItemsSource="{x:Bind ViewModel.Source}"
SelectedIndex="{x:Bind ViewModel.SelectedIndex, Mode=TwoWay}"
SelectedItem="{x:Bind ViewModel.SelectedItem, Mode=TwoWay}"
SelectedValue="{x:Bind ViewModel.SelectedValue, Mode=TwoWay}"
SelectionMode="Single"
ScrollViewer.VerticalScrollBarVisibility="Auto">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock AutomationProperties.AutomationId="{Binding}"
Text="{Binding}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

<TextBlock Grid.Row="2"
Text="Event Logs:" />
<!--<ListView Grid.Row="3"
x:Name="LogListView">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"
TextWrapping="Wrap" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>-->
<ScrollViewer Grid.Row="3"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<TextBox x:Name="EventLogs"
IsReadOnly="True"
AcceptsReturn="True"
TextWrapping="Wrap" />
</ScrollViewer>


<StackPanel Grid.Row="4">
<Button x:Name="SetSelectIndexTo0Button"
Content="Set SelectionIndex to 0"
Click="SetSelectIndexTo0" />
<Button x:Name="ClearLogsButton"
Content="Clear Logs"
Click="ClearLogs" />
</StackPanel>
</Grid>
</Page>
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Uno.UI.Samples.Controls;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace UITests.Windows_UI_Xaml_Controls.ListView
{
[SampleControlInfo("ListView", nameof(ListView_Selection_Events), description: "The sequence of events when selecting an item should happen in the same order when compared to uwp.")]
public sealed partial class ListView_Selection_Events : Page
{
private CustomViewModel ViewModel { get; } = new CustomViewModel();

public ListView_Selection_Events()
{
this.InitializeComponent();
SampleListView.SelectionChanged += (s, e) => AddLog(
"LV.SelectionChanged: (Item|Value|Index): " +
$"\n\t- lv:({Format(SampleListView.SelectedItem)}|{Format(SampleListView.SelectedValue)}|{Format(SampleListView.SelectedIndex)}), " +
$"\n\t- vm:({Format(ViewModel.SelectedItem)}|{Format(ViewModel.SelectedValue)}|{Format(ViewModel.SelectedIndex)})"
);
ViewModel.PropertyChanged += (s, e) =>
{
AddLog($"VM.PropertyChanged: [{e.PropertyName}]->{Format(GetValueFrom(e.PropertyName))}");

object GetValueFrom(string propertyName) => propertyName switch
{
nameof(CustomViewModel.SelectedItem) => ViewModel.SelectedItem,
nameof(CustomViewModel.SelectedValue) => ViewModel.SelectedValue,
nameof(CustomViewModel.SelectedIndex) => ViewModel.SelectedIndex,

_ => throw new ArgumentOutOfRangeException(propertyName),
};
};

string Format(object value) => value?.ToString() ?? "<null>";
void AddLog(string log)
{
//LogListView.Items.Add(log);
EventLogs.Text += (string.IsNullOrEmpty(EventLogs.Text) ? null : "\n") + log;
}
}
private void SetSelectIndexTo0(object sender, RoutedEventArgs e)
{
SampleListView.SelectedIndex = 0;
}
private void ClearLogs(object sender, RoutedEventArgs e)
{
//LogListView.Items.Clear();
EventLogs.Text = string.Empty;
}

// not using ViewModelBase because it would fire the same event twice, once as $"{propertyName}" and once as $"Item[{propertyName}]"...
private class CustomViewModel : global::System.ComponentModel.INotifyPropertyChanged
{
public event global::System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

public string[] Source { get; } = Enumerable.Range(0, 5).Select(x => $"Item_{x}").ToArray();

#region SelectedItem
private object _selectedItem;
public object SelectedItem
{
get => _selectedItem;
set => RaiseAndSetIfChanged(ref _selectedItem, value);
}
#endregion
#region SelectedValue
private object _selectedValue;
public object SelectedValue
{
get => _selectedValue;
set => RaiseAndSetIfChanged(ref _selectedValue, value);
}
#endregion
#region SelectedIndex
private int _selectedIndex;
public int SelectedIndex
{
get => _selectedIndex;
set => RaiseAndSetIfChanged(ref _selectedIndex, value);
}
#endregion

protected void RaiseAndSetIfChanged<T>(ref T backingField, T value, [CallerMemberName] string propertyName = null)
{
if (!EqualityComparer<T>.Default.Equals(backingField, value))
{
backingField = value;
PropertyChanged?.Invoke(this, new global::System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
using Uno.Extensions;
using Uno.UI.RuntimeTests.Helpers;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Windows.UI.Xaml.Data;

namespace Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Controls
{
Expand Down Expand Up @@ -1312,11 +1314,47 @@ await WindowHelper.WaitForEqual(0, () =>
Assert.AreEqual(null, list.SelectedItem);
}

[TestMethod]
public async Task When_Selection_Events()
{
var list = new ListView
{
ItemContainerStyle = NoSpaceContainerStyle,
ItemTemplate = FixedSizeItemTemplate
};

var items = Enumerable.Range(0, 4).Select(x => "Item_" + x).ToArray();
list.ItemsSource = items;
list.SelectedIndex = 0;

var model = new When_Selection_Events_DataContext();
list.DataContext = model;
list.SetBinding(Selector.SelectedIndexProperty, new Binding { Path = new PropertyPath(nameof(model.SelectedIndex)), Mode = BindingMode.TwoWay });
list.SetBinding(Selector.SelectedItemProperty, new Binding { Path = new PropertyPath(nameof(model.SelectedItem)), Mode = BindingMode.TwoWay });
list.SetBinding(Selector.SelectedValueProperty, new Binding { Path = new PropertyPath(nameof(model.SelectedValue)), Mode = BindingMode.TwoWay });

WindowHelper.WindowContent = list;
await WindowHelper.WaitForLoaded(list);
await WindowHelper.WaitFor(() => GetPanelChildren(list).Length == 4);

list.SelectionChanged += (s, e) =>
{
Assert.AreEqual(list.SelectedItem, "Item_1");
Assert.AreEqual(list.SelectedValue, "Item_1");
Assert.AreEqual(model.SelectedIndex, 1);
Assert.AreEqual(model.SelectedItem, "Item_1");
Assert.AreEqual(model.SelectedValue, "Item_1");
};

// update selection
list.SelectedIndex = 1;
}

private bool ApproxEquals(double value1, double value2) => Math.Abs(value1 - value2) <= 2;

private class When_Removed_From_Tree_And_Selection_TwoWay_Bound_DataContext : INotifyPropertyChanged
private class When_Removed_From_Tree_And_Selection_TwoWay_Bound_DataContext : global::System.ComponentModel.INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public event global::System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

public string[] MyItems { get; } = new[] { "Red beans", "Rice" };

Expand All @@ -1330,11 +1368,51 @@ public string MySelection
_mySelection = value;
if (changing)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(MySelection)));
PropertyChanged?.Invoke(this, new global::System.ComponentModel.PropertyChangedEventArgs(nameof(MySelection)));
}
}
}
}

private class When_Selection_Events_DataContext : global::System.ComponentModel.INotifyPropertyChanged
{
public event global::System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

#region SelectedItem
private object _selectedItem;
public object SelectedItem
{
get => _selectedItem;
set => RaiseAndSetIfChanged(ref _selectedItem, value);
}
#endregion
#region SelectedValue
private object _selectedValue;
public object SelectedValue
{
get => _selectedValue;
set => RaiseAndSetIfChanged(ref _selectedValue, value);
}
#endregion
#region SelectedIndex
private int _selectedIndex;

public int SelectedIndex
{
get => _selectedIndex;
set => RaiseAndSetIfChanged(ref _selectedIndex, value);
}
#endregion

protected void RaiseAndSetIfChanged<T>(ref T backingField, T value, [CallerMemberName] string propertyName = null)
{
if (!EqualityComparer<T>.Default.Equals(backingField, value))
{
backingField = value;
PropertyChanged?.Invoke(this, new global::System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
}
}

public partial class OnItemsChangedListView : ListView
Expand Down
Loading

0 comments on commit 22e4300

Please sign in to comment.