【问题标题】:How to access View control's from the ViewModel?如何从 ViewModel 访问 View 控件?
【发布时间】:2015-07-21 08:45:41
【问题描述】:

我遇到过这种情况,我需要从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 的精简实现如下供参考,在 cmets 中显示我如何平移以实现 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;
        }
    }
}

【问题讨论】:

  • 能否请您发布适当的 XAML 代码?
  • 将上面的 XAML 添加到问题中。
  • 您应该将每个组合框的SelectedItem 绑定到视图模型,而不是将视图模型耦合到视图。
  • 该实现的任何代码示例? ^^
  • 您不会将 UI 发送到 ViewModel。如果您想这样做,请继续并在代码隐藏中完成您的工作。如果您知道如何绑定到 ItemsSource 属性,则可以绑定到 SelectedItem 属性。相同的模式。

标签: c# design-patterns mvvm view viewmodel


【解决方案1】:

为了遵循 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}"/>

【讨论】:

  • 当然我使用了一个字符串数组来收集。如果您希望列表保持静态,这很好用,否则我会使用 ObservableCollection
【解决方案2】:

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

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

1.View中设置SelectedItem的绑定:

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

2.ViewModel中创建ComboBox属性:

        private ComboBox _selectedSubject;
        public ComboBox SelectedSubject
        {
            get { return _selectedSubject; }

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

【讨论】:

  • 这仍然是恕我直言的不良 MVVM 做法,因为您仍在 ViewModel 中引用特定的 UI 控件类型。请参阅我对更通用方法的回答。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-11
  • 1970-01-01
  • 2012-10-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多