如何使用ViewModel

时间:2017-11-03 09:17:29

标签: xamarin xamarin.forms

我有这个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;

 }

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

请注意,IsEnabled可以使用但不能使用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;
    }

}

更新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 个答案:

答案 0 :(得分:5)

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

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

最简单的解决方法是在cfsSelectValue

您可以在可见树中搜索local:CustomTextCells的所有元素,并将IsSelected设置为false,因为的每个TextCell传递给sender {1}}。

我该怎么做:

要尝试的事情:

对于您的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;
        }                    
    }
}

答案 1 :(得分: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);

}

答案 2 :(得分: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;
    }
}