using Xamarin.Forms.CustomAttributes; using System.Collections.ObjectModel; using System.ComponentModel; using System.Threading.Tasks; using System; using Xamarin.Forms.Internals; namespace Xamarin.Forms.Controls { [Preserve (AllMembers = true)] [Issue (IssueTracker.Bugzilla, 34720, "Incorrect iOS button IsEnabled when scrolling ListView with command binding ")] public class Bugzilla34720 : TestContentPage // or TestMasterDetailPage, etc ... { protected override void Init () { Title = "Test Command Binding"; _list = new ListView () { ClassId = "SampleList", // Note: Turning on and off row height does not effect the issue, // but with row heights on there is a visual glitch with the recyclyed row spacing //RowHeight = SampleViewCell.RowHeight, HasUnevenRows = true, ItemTemplate = new DataTemplate (typeof(SampleViewCell)), BackgroundColor = Color.FromHex ("E0E0E0"), VerticalOptions = LayoutOptions.FillAndExpand, HorizontalOptions = LayoutOptions.FillAndExpand, }; _list.SetBinding (ListView.ItemsSourceProperty, Binding.Create (r => r.Items)); _list.SetBinding (ListView.RefreshCommandProperty, Binding.Create (r => r.RefreshCommand)); _list.SetBinding (ListView.IsRefreshingProperty, Binding.Create (r => r.IsRefreshing)); var listViewModel = new TestListViewModel (); listViewModel.AddTestData (); BindingContext = listViewModel; _list.ItemTapped += (sender, e) => { DisplayAlert("hello", "You tapped " + e.Item.ToString(), "OK", "Cancel"); }; var btnDisable = new Button () { Text = "Disable ListView", }; btnDisable.Clicked += (object sender, EventArgs e) => { if (_list.IsEnabled == true){ _list.IsEnabled = false; btnDisable.Text = "Enable ListView"; } else { _list.IsEnabled = true; btnDisable.Text = "Disable ListView"; } }; Content = new StackLayout { VerticalOptions = LayoutOptions.FillAndExpand, Children = { btnDisable, _list } }; } ListView _list; [Preserve (AllMembers = true)] public class SampleViewCell : ViewCell { public static int RowHeight = 120; static int s_idSeed; int id; public SampleViewCell () { id = s_idSeed++; var grid = new Grid { ClassId = "SampleCard", Padding = new Thickness (7, 10), RowSpacing = 0, ColumnSpacing = 0, RowDefinitions = { new RowDefinition{ Height = new GridLength (80, GridUnitType.Absolute) }, new RowDefinition{ Height = new GridLength (40, GridUnitType.Absolute) }, }, }; var head = new SampleHeaderView (); grid.Children.Add (head); var foot = new SampleListActionView (); Grid.SetRow (foot, 1); grid.Children.Add (foot); View = grid; } #region Testing Code // Note this block can be removed it is just used to observing the ViewCell creation. int _counter; protected override void OnAppearing () { base.OnAppearing (); _counter++; System.Diagnostics.Debug.WriteLine ("OnAppearing {0}, {1}", id, _counter); } protected override void OnDisappearing () { base.OnDisappearing (); _counter--; System.Diagnostics.Debug.WriteLine ("OnDisappearing {0}, {1}", id, _counter); } #endregion public class SampleHeaderView : ContentView { public SampleHeaderView () { //+-----------+----------------+ //| 1 | | //+-----------+----------------+ //| 2 | | //+-----------+----------------+ var grid = new Grid { Padding = new Thickness (5, 5, 5, 1), RowSpacing = 1, ColumnSpacing = 1, BackgroundColor = Color.FromHex ("FAFAFA"), RowDefinitions = { new RowDefinition{ Height = new GridLength (1.25, GridUnitType.Star) }, new RowDefinition{ Height = new GridLength (0.8, GridUnitType.Star) }, }, ColumnDefinitions = { new ColumnDefinition{ Width = new GridLength (1, GridUnitType.Star) }, new ColumnDefinition{ Width = new GridLength (1, GridUnitType.Star) }, } }; //1 number var materialNumber = new Label () { VerticalOptions = LayoutOptions.StartAndExpand, HorizontalOptions = LayoutOptions.StartAndExpand }; Grid.SetColumnSpan (materialNumber, 2); materialNumber.SetBinding (Label.TextProperty, Binding.Create (vm => vm.Number)); grid.Children.Add (materialNumber); //2 Description var materialDescription = new Label () { VerticalOptions = LayoutOptions.StartAndExpand, HorizontalOptions = LayoutOptions.StartAndExpand }; Grid.SetColumnSpan (materialDescription, 2); Grid.SetRow (materialDescription, 1); materialDescription.SetBinding (Label.TextProperty, Binding.Create (vm => vm.Description)); //grid.Children.Add (materialDescription); //3 Approve Label var canApprove = new Label () { VerticalOptions = LayoutOptions.StartAndExpand, HorizontalOptions = LayoutOptions.StartAndExpand }; Grid.SetColumn (canApprove, 1); Grid.SetRow (canApprove, 1); canApprove.SetBinding (Label.TextProperty, Binding.Create (vm => vm.CanApprove, stringFormat: "Can Approve: {0}")); grid.Children.Add (canApprove); //3 Approve Label var canDeny = new Label () { VerticalOptions = LayoutOptions.StartAndExpand, HorizontalOptions = LayoutOptions.StartAndExpand }; Grid.SetColumn (canDeny, 0); Grid.SetRow (canDeny, 1); canDeny.SetBinding (Label.TextProperty, Binding.Create (vm => vm.CanDeny, stringFormat: "Can Deny: {0}")); grid.Children.Add (canDeny); Content = grid; } } public class SampleListActionView : ContentView { public SampleListActionView () { var overallGrid = new Grid { BackgroundColor = Color.FromHex ("FAFAFA"), HorizontalOptions = LayoutOptions.FillAndExpand, VerticalOptions = LayoutOptions.CenterAndExpand, ColumnDefinitions = { new ColumnDefinition{ Width = new GridLength (1, GridUnitType.Star) }, new ColumnDefinition{ Width = new GridLength (1, GridUnitType.Star) }, } }; var grid = new Grid { VerticalOptions = LayoutOptions.FillAndExpand, HorizontalOptions = LayoutOptions.FillAndExpand, ColumnDefinitions = { new ColumnDefinition{ Width = new GridLength (1, GridUnitType.Star) }, new ColumnDefinition{ Width = new GridLength (1, GridUnitType.Star) }, } }; // 1 Deny var denyBtn = new Button { ClassId = "btnReject", Text = "DENY", HorizontalOptions = LayoutOptions.FillAndExpand, VerticalOptions = LayoutOptions.FillAndExpand }; denyBtn.SetBinding (Button.CommandProperty, Binding.Create (r => r.DenyCommand)); grid.Children.Add (denyBtn); // 2 Approve var approveBtn = new Button { ClassId = "btnApprove", Text = "Approve", HorizontalOptions = LayoutOptions.FillAndExpand, VerticalOptions = LayoutOptions.FillAndExpand, }; Grid.SetColumn (approveBtn, 1); approveBtn.SetBinding (Button.CommandProperty, Binding.Create (r => r.ApproveCommand)); grid.Children.Add (approveBtn); Grid.SetColumn (grid, 1); overallGrid.Children.Add (grid); Content = overallGrid; } } } [Preserve (AllMembers = true)] public class TestListViewModel : INotifyPropertyChanged { Collection _items = new ObservableCollection (); public void AddTestData () { for (int i = 0; i < 20; i++) { _items.Add (new TestViewModel () { Description = string.Format ("Sample Description {0}", i), Number = (i + 1).ToString (), CanApprove = i % 2 == 0, CanDeny = i % 2 == 0 }); } RaisePropertyChanged ("Items"); } public Collection Items { get { return _items; } set { _items = value; } } public Command RefreshCommand { get { return new Command (OnRefresh); } } public bool IsRefreshing { get; set; } async void OnRefresh () { IsRefreshing = true; RaisePropertyChanged ("IsRefreshing"); _items.Clear (); await Task.Delay (1000); AddTestData (); IsRefreshing = false; RaisePropertyChanged ("IsRefreshing"); } #region INotifyPropertyChanged implementation public event PropertyChangedEventHandler PropertyChanged; #endregion protected virtual void RaisePropertyChanged (string propertyName) { PropertyChangedEventHandler propertyChanged = PropertyChanged; if (propertyChanged != null) { propertyChanged (this, new PropertyChangedEventArgs (propertyName)); } } } [Preserve (AllMembers = true)] public class TestViewModel { public string Number { get; set; } public string Description { get; set; } bool _canApprove; public bool CanApprove { get{ return _canApprove; } set { _canApprove = value; ApproveCommand.ChangeCanExecute (); } } bool _canDeny; public bool CanDeny { get { return _canDeny; } set { _canDeny = value; DenyCommand.ChangeCanExecute (); } } public Command ApproveCommand { get { return new Command (OnApprove, () => CanApprove); } } public Command DenyCommand { get { return new Command (OnDeny, () => CanDeny); } } async void OnApprove () { await Application.Current.MainPage.DisplayAlert ("Approve", string.Format ("Can Approve {0} {1}", Number, CanApprove), "Ok"); } async void OnDeny () { await Application.Current.MainPage.DisplayAlert ("Deny", string.Format ("Can Deny {0} {1}", Number, CanDeny), "Ok"); } } } }