如何从 ViewModel 访问视图控件?

How to access View control's from the ViewModel?

我遇到过这样的情况,我需要从 ViewModel 访问 View 中的控件。为了编写一种方法,将 ComboBox 中的所选项目添加到列表中。

我的问题是如何从 ViewModel 访问 View 控件?我应该遵循某种特定的设计模式来允许这样做吗?

下面的方法编码在后面的 View's 代码中,我知道如果遵循 MVVM 模式,由于涉及紧密耦合,这是不好的做法。这就是为什么我打算将此方法移动到 ViewModel.

该方法的目的是从两个 ComboBoxes 中取出当前选择的项目并添加到一个 Key/Value List

public void AddGradeSubjectChoiceToList()
{
    string SelectedSubjectName = "null data";
    int SelectedPoints = 01;


    SelectedSubjectName = subjectCmbBx.SelectedItem.ToString();

    try {

    SelectedPoints = int.Parse(ordinaryGradeCmbBx.SelectedValue.ToString());

    }
    catch (Exception e)
    {
        //log error here..

    }

    List<StringKeyValue> SubjectPointKVTemp = new List<StringKeyValue>();

    //Add selected pair to list
    SubjectPointKVTemp.Add(new StringKeyValue { Key = SelectedSubjectName, Value = SelectedPoints });

    SubjectPointKV = SubjectPointKVTemp;

}

MainPage 的 XAML 是这样设置的,有两个组合框,用于科目和年级。 addGrade 按钮将调用将所选对添加到列表的方法:

<Page x:Class="LC_Points.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:Core="using:Microsoft.Xaml.Interactions.Core"
      xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"
      xmlns:converter="using:LC_Points.Converter"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
      xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
      xmlns:local="using:LC_Points"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
      DataContext="{Binding Source={StaticResource Locator}}"
      mc:Ignorable="d">

    <Page.Resources>
        <converter:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
        <converter:BoolToNonVisibilityConverter x:Key="BoolToNonVisibilityConverter" />
    </Page.Resources>


    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="40*" />
            <RowDefinition Height="20*" />
            <RowDefinition Height="30*" />
            <RowDefinition Height="30*" />
            <RowDefinition Height="20*" />
            <RowDefinition Height="20*" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="4*" />
            <ColumnDefinition Width="3*" />
        </Grid.ColumnDefinitions>


        <ComboBox x:Name="subjectCmbBx"
                  Grid.Row="1"
                  Grid.ColumnSpan="2"
                  Width="174"
                  HorizontalAlignment="Left"
                  VerticalAlignment="Top"
                  DisplayMemberPath="Subject"
                  Header="Subjects"
                  ItemsSource="{Binding Subjects}"
                  PlaceholderText="Pick a subject" />


        <ComboBox x:Name="ordinaryGradeCmbBx"
                  Grid.Row="1"
                  Grid.Column="0"
                  Grid.ColumnSpan="2"
                  Width="170"
                  HorizontalAlignment="Right"
                  DisplayMemberPath="Key"
                  Header="Grades"
                  ItemsSource="{Binding OrdinaryGradePointKV}"
                  PlaceholderText="Pick a grade"
                  Visibility="{Binding IsHigherToggled,
                                       Mode=TwoWay,
                                       Converter={StaticResource BoolToNonVisibilityConverter}}" />


        <Button x:Name="addGradeBtn"
                Grid.Row="2"
                HorizontalAlignment="Left"
                Command="{Binding Path=AddGradeCommand}"
                Content="Add Grade" />


        <ToggleButton x:Name="ordinaryTglBtn"
                      Grid.Row="3"
                      Grid.ColumnSpan="2"
                      HorizontalAlignment="Center"
                      Content="Ordinary"
                      IsChecked="{Binding IsOrdinaryToggled,
                                          Mode=TwoWay}" />


    </Grid>


</Page>

ViewModel 的精简实现如下供参考,在注释中显示了我如何平移实现 AddGradeSubjectChoiceToList() 方法:

namespace LC_Points.ViewModel
{
    public class MainViewModel : ViewModelBase
    {
        private ScoreModel _scoreModel;

        /// <summary>
        /// Initializes a new instance of the MainViewModel class.
        /// </summary>
        public MainViewModel(ScoreModel GradeModel)
        {

            _scoreModel = GradeModel;

            //call methods to initilise list data
            GetSubjectTypes();
            GetOrdinaryGradePairs();
        }

        public List<ScoreModel> Subjects { get; set; }
        public List<StringKeyValue> OrdinaryGradePointKV { get; set; }

        //ordinary toggle button bool
        private bool _isOrdinaryToggled;
        public bool IsOrdinaryToggled
        {
            get
            {
                return _isOrdinaryToggled;
            }
            set
            {
                _isOrdinaryToggled = value;
                RaisePropertyChanged("IsOrdinaryToggled");
            }
        }

        //Need to add same method from code behind to VM here
        //but don't have access to the View's controlsMethod to store Subject and Grade from Combo Boxes
        public void AddGradeSubjectChoiceToList()
        {

        }

        //This Relay Command is tied to the button in the View, that will be used
        //to call the AddGradeSubjectChoiceToList method
        RelayCommand addGradeCommand;
        public RelayCommand AddGradeCommand
        {
            get
            {
                if (addGradeCommand == null)
                {
                    addGradeCommand = new RelayCommand(() =>
                    {
                       AddGradeSubjectChoiceToList
                    });
                }
                return addGradeCommand;
            }
        }

        public class StringKeyValue
        {
            public string Key { get; set; }
            public int Value { get; set; }
        }

        public void GetOrdinaryGradePairs()
        {
            List<StringKeyValue> ordinaryGradePointKVTemp = new List<StringKeyValue>();


            ordinaryGradePointKVTemp.Add(new StringKeyValue { Key = "A1", Value = 60 });
            ordinaryGradePointKVTemp.Add(new StringKeyValue { Key = "A2", Value = 50 });
            ordinaryGradePointKVTemp.Add(new StringKeyValue { Key = "B1", Value = 45 });
            ordinaryGradePointKVTemp.Add(new StringKeyValue { Key = "B2", Value = 40 });
            ordinaryGradePointKVTemp.Add(new StringKeyValue { Key = "B3", Value = 35 });
            ordinaryGradePointKVTemp.Add(new StringKeyValue { Key = "C1", Value = 30 });
            ordinaryGradePointKVTemp.Add(new StringKeyValue { Key = "C2", Value = 25 });
            ordinaryGradePointKVTemp.Add(new StringKeyValue { Key = "C3", Value = 20 });


            OrdinaryGradePointKV = ordinaryGradePointKVTemp;

        }

        public void GetSubjectTypes()
        {
            List<ScoreModel> subjectList = new List<ScoreModel>();

            // Adding Subjects to List
            subjectList.Add(new ScoreModel { Subject = "Accounting" });
            subjectList.Add(new ScoreModel { Subject = "Agricultural Economics" });
            subjectList.Add(new ScoreModel { Subject = "Agricultural Science" });
            subjectList.Add(new ScoreModel { Subject = "Ancient Greek" });
            subjectList.Add(new ScoreModel { Subject = "Applied Math" });
            subjectList.Add(new ScoreModel { Subject = "Arabic" });
            subjectList.Add(new ScoreModel { Subject = "Art" });
            subjectList.Add(new ScoreModel { Subject = "Artistic & Creative Group" });
            subjectList.Add(new ScoreModel { Subject = "Biology" });
            subjectList.Add(new ScoreModel { Subject = "Business" });

            Subjects = subjectList;
        }
    }
}

正如上面的评论所指出的,在遵循 MVVM 模式时,将 View 耦合到 ViewModel 的解决方案是不好的做法。

适当的解决方案是为 ComboBox 的 SelectedItem 设置一个 属性 并将视图绑定到它,以便可以在 VM 中利用所选项目。

1.View 中设置绑定到 SelectedItem:

SelectedItem="{Binding SelectedSubject,Mode=TwoWay}"

2.ViewModel:

中创建组合框 属性
        private ComboBox _selectedSubject;
        public ComboBox SelectedSubject
        {
            get { return _selectedSubject; }

            set
            {
                _selectedSubject = value;
                RaisePropertyChanged("SelectedSubject");
            }
        }

为了遵循 MVVM,您应该使用泛型并将它们绑定到控件。

这是我的一个 ViewModel 中的示例:

private string[] _optionItems;
public string[] OptionItems
{
    get
    {
        return _optionItems;
    }
    set
    {
        if (_optionItems == value)
            return;

        _optionItems = value;
        OnPropertyChanged();
    }
}

private string _selectedOption;
public string SelectedOption
{
    get
    {
        return _selectedOption;
    }
    set
    {
        if (_selectedOption == value)
            return;

        _selectedOption = value;
        OnPropertyChanged();
    }
}

这里是 XAML 代码:

<ComboBox ItemsSource="{Binding OptionItems}" SelectedItem="{Binding SelectedOption}"/>