首页 文章

如何使用ViewModel实现单击以进行选择

提问于
浏览
6

我有这个XAML显示6个TextCells,它们可以显示复选标记 . 它们也启用或未启用:

<TableSection Title="Front Side" x:Name="cfsSection">
   <local:CustomTextCell Text="{Binding [0].Name}" IsChecked="{Binding [0].IsSelected}" IsEnabled="{Binding [0].IsSelected, Converter={StaticResource InverseBoolConverter} } "Tapped="cfsSelectValue"/>
   <local:CustomTextCell Text="{Binding [1].Name}" IsChecked="{Binding [1].IsSelected}" IsEnabled="{Binding [1].IsSelected, Converter={StaticResource InverseBoolConverter} } "Tapped="cfsSelectValue"/>
   <local:CustomTextCell Text="{Binding [2].Name}" IsChecked="{Binding [2].IsSelected}" IsEnabled="{Binding [2].IsSelected, Converter={StaticResource InverseBoolConverter} } "Tapped="cfsSelectValue"/>
   <local:CustomTextCell Text="{Binding [3].Name}" IsChecked="{Binding [3].IsSelected}" IsEnabled="{Binding [3].IsSelected, Converter={StaticResource InverseBoolConverter} } "Tapped="cfsSelectValue"/>
   <local:CustomTextCell Text="{Binding [4].Name}" IsChecked="{Binding [4].IsSelected}" IsEnabled="{Binding [4].IsSelected, Converter={StaticResource InverseBoolConverter} } "Tapped="cfsSelectValue"/>
   <local:CustomTextCell Text="{Binding [5].Name}" IsChecked="{Binding [5].IsSelected}" IsEnabled="{Binding [5].IsSelected, Converter={StaticResource InverseBoolConverter} } "Tapped="cfsSelectValue"/>
</TableSection>

我认为背后的代码相当简单 . 它声明了一个SSVViewModel数组,绑定导致文本显示:

SSVViewModel[] CFS = new[] {
        new SSVViewModel {Id = 0, Name=LANG.English.Text(), IsSelected = false},
        new SSVViewModel {Id = 1, Name=LANG.Romaji.Text(), IsSelected = false},
        new SSVViewModel {Id = 2, Name=LANG.Kana.Text(), IsSelected = false},
        new SSVViewModel {Id = 3, Name=LANG.Kanji.Text(), IsSelected = false},
        new SSVViewModel {Id = 4, Name=LANG.KanjiKana.Text(), IsSelected = false},
        new SSVViewModel {Id = 5, Name=LANG.KanaKanji.Text(), IsSelected = false},
    };

单击一个单元格时,将调用此函数:

void cfsSelectValue(object sender, EventArgs e) 
{
        var cell = sender as TextCell;
        if (cell == null)
            return;

        var selected = cell.Text;

        foreach (var setting in CFS)
            setting.IsSelected = false;

        foreach (var setting in CFS)
            if (setting.Name == selected)
                setting.IsSelected = true;

 }

但是,当单击前两个单元格中的任何一个时,都显示为已选中 . 所有其他单元格点击工作正常 . 在我的代码的另一部分中,我使用了类似的构造,它是最后两个不起作用的单元格 .

Note that the IsEnabled works but not the IsChecked

任何人都可以看到为什么点击前两个单元格可能会出现任何问题 . 我已经多次使用调试器,但我仍然看不出可能出错的地方 . 当然,将IsSelected设置为false的代码应该导致除一个单元格之外的所有代码都显示为已选中 .

请注意,在调试此行时:setting.IsSelected = false;这一行:setting.IsSelected = true;然后一切都显示为正确的单元格,IsSelected设置为true,其他设置为false . 只是当我看到显示器时,看起来绑定对前两个单元格不起作用 .

这是我正在使用的viewModel代码:

public class SSVViewModel: ObservableProperty
{
    private int id;
    private string name;
    private bool isSelected;

    public int Id
    {
        get
        {
            return id;
        }
        set
        {
            if (value != id)
            {
                id = value;
                NotifyPropertyChanged("Id");
            }
        }
    }

    public string Name
    {
        get
        {
            return name;
        }
        set
        {
            if (value != name)
            {
                name = value;
                NotifyPropertyChanged("Name");
            }
        }
    }

    public bool IsSelected
    {
        get
        {
            return isSelected;
        }
        set
        {
            if (value != isSelected)
            {
                isSelected = value;
                NotifyPropertyChanged("IsSelected");
            }
        }
    }
}

这是CustomTextCellRenderer的代码

public class CustomTextCellRenderer : TextCellRenderer
{
    UITableViewCell _nativeCell;

    public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell, UITableView tv)
    {
        _nativeCell = base.GetCell(item, reusableCell, tv);
        var formsCell = item as CustomTextCell;

        if (formsCell != null)
        {
            formsCell.PropertyChanged -= OnPropertyChanged;
            formsCell.PropertyChanged += OnPropertyChanged;
        }

        SetCheckmark(formsCell);
        SetTap(formsCell);

        return _nativeCell;
    }

    void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        var formsCell = sender as CustomTextCell;
        if (formsCell == null)
            return;

        if (e.PropertyName == CustomTextCell.IsCheckedProperty.PropertyName)
        {
            SetCheckmark(formsCell);
        }

        if (e.PropertyName == CustomTextCell.NoTapProperty.PropertyName)
        {
            SetTap(formsCell);
        }
    }

    private void SetCheckmark(CustomTextCell formsCell)
    {
        if (formsCell.IsChecked)
            _nativeCell.Accessory = UITableViewCellAccessory.Checkmark;
        else
            _nativeCell.Accessory = UITableViewCellAccessory.None;
    }

    private void SetTap(CustomTextCell formsCell)
    {
        if (formsCell.NoTap)
            _nativeCell.SelectionStyle = UITableViewCellSelectionStyle.None;
        else
            _nativeCell.SelectionStyle = UITableViewCellSelectionStyle.Default;
    }

}

Update 1

<local:CustomTextCell           Text="{Binding [0].Name}" IsChecked="{Binding [0].IsSelected}" Tapped="cfsSelectValue" CommandParameter="0" />
<local:CustomTextCell           Text="{Binding [1].Name}" IsChecked="{Binding [1].IsSelected}" Tapped="cfsSelectValue" CommandParameter="1" />
<local:CustomTextCell           Text="{Binding [2].Name}" IsChecked="{Binding [2].IsSelected}" Tapped="cfsSelectValue" CommandParameter="2" />
<local:CustomTextCell           Text="{Binding [3].Name}" IsChecked="{Binding [3].IsSelected}" Tapped="cfsSelectValue" CommandParameter="3" />
<local:CustomTextCell           Text="{Binding [4].Name}" IsChecked="{Binding [4].IsSelected}" Tapped="cfsSelectValue" CommandParameter="4" />

由于问题已经写好,我已经停止使用它来进行Lang选择,但它仍然在代码的另一部分中使用,我试图放入一些调试点 . 这是我做的:

我将此添加到iOS自定义渲染器:

private void SetCheckmark(CustomTextCell formsCell)
    {
        if (formsCell.IsChecked)
        {
            _nativeCell.Accessory = UITableViewCellAccessory.Checkmark;
            Debug.WriteLine(_nativeCell.TextLabel.Text + " checked");
        }
        else
        {
            _nativeCell.Accessory = UITableViewCellAccessory.None;
            Debug.WriteLine(_nativeCell.TextLabel.Text + " unchecked");
        }
    }

这是我点击JLPT N2时的结果:

Category Group unchecked
Category unchecked
All Available Words unchecked
Japanese for Busy People 1 unchecked
Japanese for Busy People 2 unchecked
Japanese for Busy People 3 unchecked
JLPT Level N5 unchecked
JLPT Level N4 unchecked
JLPT Level N3 unchecked
JLPT Level N2 unchecked
JLPT Level N1 checked
JLPT Level N2 checked
JLPT Level N3 checked
JLPT Level N2 checked
JLPT Level N2 checked
JLPT Level N2 checked
JLPT Level N2 checked
JLPT Level N2 checked
JLPT Level N2 checked
JLPT Level N2 checked
JLPT Level N2 checked
JLPT Level N2 checked
JLPT Level N2 checked
JLPT Level N2 checked
JLPT Level N2 checked
JLPT Level N2 checked
JLPT Level N2 checked
JLPT Level N2 checked
JLPT Level N1 unchecked
JLPT Level N1 unchecked
JLPT Level N1 unchecked
JLPT Level N1 unchecked
JLPT Level N1 unchecked
JLPT Level N1 unchecked
JLPT Level N1 unchecked
JLPT Level N1 unchecked
JLPT Level N1 unchecked
JLPT Level N1 unchecked
JLPT Level N1 unchecked
JLPT Level N1 unchecked
JLPT Level N1 unchecked
JLPT Level N1 unchecked
JLPT Level N1 unchecked

这根本不是我的预期 .

在屏幕上,我看到正如预期的那样N2被禁用但是N3和N2旁边有一个复选标记 .

不确定这是否有帮助,但我注意到使用的iOS渲染器代码与我在其他地方使用的类似代码不同 . 例如,这是一个不同的iOS渲染器 . 代码看起来非常不同 . 我意识到功能不同但是这个有 cell = tv.DequeueReusableCell(fullName) as CellTableViewCell; 之类的东西

public class TextCellCustomRenderer : TextCellRenderer
{
    CellTableViewCell cell;
    public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell, UITableView tv)
    {
        var textCell = (TextCell)item;
        var fullName = item.GetType().FullName;
        cell = tv.DequeueReusableCell(fullName) as CellTableViewCell;

        if (cell == null)
        {
            cell = new CellTableViewCell(UITableViewCellStyle.Value1, fullName);
        }
        else
        {
            cell.Cell.PropertyChanged -= cell.HandlePropertyChanged;
            //cell.Cell.PropertyChanged -= Current_PropertyChanged;
        }

        cell.Cell = textCell;
        textCell.PropertyChanged += cell.HandlePropertyChanged;
        cell.PropertyChanged = this.HandlePropertyChanged;
        cell.SelectionStyle = UITableViewCellSelectionStyle.None;
        cell.TextLabel.Text = textCell.Text;
        cell.DetailTextLabel.Text = textCell.Detail;
        cell.ContentView.BackgroundColor = UIColor.White;


        switch (item.StyleId)
        {
            case "checkmark":
                cell.Accessory = UIKit.UITableViewCellAccessory.Checkmark;
                break;
            case "detail-button":
                cell.Accessory = UIKit.UITableViewCellAccessory.DetailButton;
                break;
            case "detail-disclosure-button":
                cell.Accessory = UIKit.UITableViewCellAccessory.DetailDisclosureButton;
                break;
            case "disclosure":
                cell.Accessory = UIKit.UITableViewCellAccessory.DisclosureIndicator;
                break;
            case "none":
            default:
                cell.Accessory = UIKit.UITableViewCellAccessory.None;
                break;
        }

        //UpdateBackground(cell, item);

        return cell;
    }

    void checkAccessoryVisibility() {

    }

}

3 回答

  • 5

    根据日志语句,看起来property-changed-handler的取消订阅逻辑不能按预期工作 .

    使用 TextCellRenderer ,我们不需要显式订阅property-changed-event,因为有一个可在此上下文中重用的基本可覆盖方法 HandlePropertyChanged .

    更改渲染器代码以使用此方法(similar to this answer)应该有希望解决此问题:

    public class CustomTextCellRenderer : TextCellRenderer
    {
        public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell, UITableView tv)
        {
            var nativeCell = base.GetCell(item, reusableCell, tv);
    
            if (item is CustomTextCell formsCell)
            {
                SetCheckmark(nativeCell, formsCell);
                SetTap(nativeCell, formsCell);
            }
    
            return nativeCell;
        }
    
        protected override void HandlePropertyChanged(object sender, PropertyChangedEventArgs args)
        {
            base.HandlePropertyChanged(sender, args);
    
            System.Diagnostics.Debug.WriteLine($"HandlePropertyChanged {args.PropertyName}");
    
            var nativeCell = sender as CellTableViewCell;
            if (nativeCell?.Element is CustomTextCell formsCell)
            {
                if (args.PropertyName == CustomTextCell.IsCheckedProperty.PropertyName)
                    SetCheckmark(nativeCell, formsCell);
                else if (args.PropertyName == CustomTextCell.NoTapProperty.PropertyName)
                    SetTap(nativeCell, formsCell);
            }
        }
    
        void SetCheckmark(UITableViewCell nativeCell, CustomTextCell formsCell)
        {
            if (formsCell.IsChecked)
                nativeCell.Accessory = UITableViewCellAccessory.Checkmark;
            else
                nativeCell.Accessory = UITableViewCellAccessory.None;
        }
    
        void SetTap(UITableViewCell nativeCell, CustomTextCell formsCell)
        {
            if (formsCell.NoTap)
                _nativeCell.SelectionStyle = UITableViewCellSelectionStyle.None;
            else
                _nativeCell.SelectionStyle = UITableViewCellSelectionStyle.Default;
        }
    }
    
  • 5

    我找不到导致该行为的错误,所以这是我对此的想法:

    您描述的行为听起来更像是 RadioButton . Since there are none in Xamarin.Forms您可以创建自己的,使用包或获得解决方法 .

    最简单的解决方法将在您的 cfsSelectValue 中 .

    您可以在可视树中搜索 local:CustomTextCells 的所有元素,并为每个 TextCell 设置 IsSelected 为false,这是 not 作为 sender 传递的_1291120_ .


    How I would do it:

    要尝试的事情:

    对于ViewModel,使用ObservableCollection而不是 Array

    private ObservableCollection<SSVViewModel> viewModels = new ObservableCollection<SSVViewModel>()
    {
        new SSVViewModel {Id = 0, Name=LANG.English.Text()},
        new SSVViewModel {Id = 1, Name=LANG.Romaji.Text()},
        new SSVViewModel {Id = 2, Name=LANG.Kana.Text()},
        new SSVViewModel {Id = 3, Name=LANG.Kanji.Text()},
        new SSVViewModel {Id = 4, Name=LANG.KanjiKana.Text()},
        new SSVViewModel {Id = 5, Name=LANG.KanaKanji.Text()},
    };
    

    INotifyPropertyChanged 而不是 ObservableProperty 派生ViewModel

    public class SSVViewModel : INotifyPropertyChanged
    {
        private int id;
        private string name;
        private bool isSelected = false; //Set the default here
    
        public int Id
        {
            get
            {
                return id;
            }
            set
            {
                if (value != id)
                {
                    id = value;
                    OnPropertyChanged();
                }
            }
        }
    
        public string Name
        {
            get
            {
                return name;
            }
            set
            {
                if (value != name)
                {
                    name = value;
                    OnPropertyChanged();
                }
            }
        }
    
        public bool IsSelected
        {
            get
            {
                return isSelected;
            }
            set
            {
                if (value != isSelected)
                {
                    isSelected = value;
                    OnPropertyChanged();
                }
            }
        }
    
    
        #region Implementation of INotifyPropertyChanged
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        private void OnPropertyChanged([CallerMemberName] string propertyname = null)
        {
            if(PropertyChanged != null)
            {
                PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyname));
            }
        }
    
        #endregion
    }
    

    cfsSelectValue 更改为:

    public void cfsSelectValue(object sender, EventArgs e)
    {
        //GetCurrentCell
        CustomTextCell cell = sender as CustomTextCell;
        if (cell == null)
        {
            return;
        }            
    
        foreach (SSVViewModel viewModel in CFS)
        {
            /*
            Since there is no Tag Property we gotta use something different
            you could use `CommandParameter` since it is of type object
    
            */
            if (viewModel.Name == cell.Text && viewModel.Id == int.Parse(cell.CommandParameter.ToString()))
            {
                viewModel.IsSelected = true;
            }
            else
            {
                viewModel.IsSelected = false;
            }                    
        }
    }
    
  • 5

    代码看起来对我来说 - 我想象这种情况发生的唯一原因是字符串比较逻辑没有按预期工作 .

    也许基于参考的比较可能会解决问题 . 即将您的XAML更改为:

    <TableSection Title="Front Side" x:Name="cfsSection">
       <local:CustomTextCell BindingContext="{Binding [0]}" Text="{Binding Name}" IsChecked="{Binding IsSelected}" Tapped="cfsSelectValue"/>
       <local:CustomTextCell BindingContext="{Binding [1]}" Text="{Binding Name}" IsChecked="{Binding IsSelected}" Tapped="cfsSelectValue"/>
       <local:CustomTextCell BindingContext="{Binding [2]}" Text="{Binding Name}" IsChecked="{Binding IsSelected}" Tapped="cfsSelectValue"/>
       <local:CustomTextCell BindingContext="{Binding [3]}" Text="{Binding Name}" IsChecked="{Binding IsSelected}" Tapped="cfsSelectValue"/>
       <local:CustomTextCell BindingContext="{Binding [4]}" Text="{Binding Name}" IsChecked="{Binding IsSelected}" Tapped="cfsSelectValue"/>
       <local:CustomTextCell BindingContext="{Binding [5]}" Text="{Binding Name}" IsChecked="{Binding IsSelected}" Tapped="cfsSelectValue"/>
    </TableSection>
    

    并点击处理程序:

    void cfsSelectValue(object sender, EventArgs e)
    {
        var cell = sender as TextCell;
        if (cell == null)
            return;
    
        var selected = cell.BindingContext;
    
        foreach (var setting in CFS)
            setting.IsSelected = (setting == selected);
    
    }
    

相关问题