理解MVVM - 如何绑定数据视图↔ViewModel+ catch按下视图并在viewModel中启动函数

时间:2017-03-30 20:22:17

标签: c# .net wpf xaml mvvm

我是C#的新手并创建了一个工作正常的简单应用程序,但我想使用模式MVVM学习C#。所以我试图将我的应用程序迁移到MVVM并且我感到困惑

我的目标很简单:

1)当打开时,应用程序扫描文件夹并索引格式化的所有文件 " [编号] [姓名]" - 它工作正常!

2)我有一个只有一个textBox的窗口。用户键入一个数字,然后按ENTER键。在这一刻,我有一个CatalogViewModel,它是一个集合File,应该选择textBox中数字指定的文件并打开它。

问题1:在MVVM中,我无法将数据从我的视图Main传递到我的ViewModel CatalogViewModel(我不确定我是否可以做得正确)

问题2:我无法处理ENTER键并触发CatalogViewModel

内的功能

我对MVVM有点困惑,无法继续。我知道这很简单。请问,如何解决这个问题(请详细说明,我是C#及其所有概念的初学者)

更新1

尝试了janonimus'问题1的解决方案但是数据绑定只是一种方式。 VM的值转到VIEW,但VIEW上的更改不会转到VM。 我以这种方式实施了INotifyPropertyChanged

using Prism.Mvvm;
...    

    public class CatalogViewModel: BindableBase
    {

        private string selectedValue = "100";
        public string SelectedValue
        {
            get { return selectedValue; }
            set { SetProperty(ref selectedValue, value); }
        }

但是Databind变成了ON WAY XAML

<TextBox x:Name="tbSelectedValue" Text="{Binding SelectedValue, Mode=TwoWay}"

更新2

找到问题解决方案1 ​​。 janonimus提供的代码只能以单向方式工作,因为TextBox.Text的默认行为是在失去焦点时upadte,但在我的情况下它永远不会失去焦点see this post

以下代码解决了问题1:

Text="{Binding Path=SelectedValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}

问题2 可以通过Pedro Silva的回答解决

if (e.Key == Key.Enter && tbSelectedValue.Text != String.Empty)
            {
                vm.OpenSelectedFile();
                tbSelectedValue.Text = String.Empty;
            }

但我想使用ICommand以更加软化的方式实现它。 按照janonimus发送的建议,我创建了BaseCommand类Exactly like this但是当我调用函数OpenSelectedFile

时会抛出不匹配错误
private BaseCommand<CatalogViewModel> _selectFileCommand;
public ICommand SelectFileCommand
{
    get
    {
        if (_selectFileCommand == null)
        {
            _selectFileCommand = new BaseCommand<CatalogViewModel>(OpenSelectedFile, true);
        }
        return _selectFileCommand;
    }
}
public void OpenSelectedFile()
{
    try
    {
        OpenFileByNumber(Int32.Parse(SelectedValue));
    }
    catch (Exception e)
    {
        MessageBox.Show("Número inválido: \'" + SelectedValue + "\"",
           "ERRO", MessageBoxButton.OK, MessageBoxImage.Warning);
    }

}

这是我的参考代码......

Main.xaml.cs

namespace SLMT.Views
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class Main : Window
    {

        public Main()
        {
            InitializeComponent();
            DataContext = new CatalogViewModel();
            chosenNumber.Focus();
        }



        // Permite inserir somente números
        private void ChosenNumber_PreviewTextInput(object sender, TextCompositionEventArgs e)
        {
            Regex regex = new Regex("[^ 0-9]+");
            e.Handled = regex.IsMatch(e.Text);
        }

        private void ChosenNumber_KeyUp(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter && chosenNumber.Text != String.Empty)
            {
                //catalog.OpenFileByNumber(ConvertToInt(numeroEscolhido.Text));
                //catalog.OpenSelectedFile(); // Will become someting like this
                chosenNumber.Text = String.Empty;            

            }
        }

        private int ConvertToInt(string value)
        {
            try
            {
                var str = value.Replace(" ", String.Empty);
                return Int32.Parse(str);
            }
            catch (Exception exc)
            {
                MessageBox.Show("O número: \"" + chosenNumber.Text + "\" é inválido", "ERRO", MessageBoxButton.OK, MessageBoxImage.Error);
                chosenNumber.Text = String.Empty;

                return 0;
            }
        }


        /// <summary>
        /// Controll what wil lhappen if some KEYS are pressed on APP
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void GMain_KeyUp(object sender, KeyEventArgs e)
        {

            switch (e.Key){

                case Key.Escape:
                    Environment.Exit(0);
                    break;

                case Key.F1:
                    //wListFiles = new ListFiles(catalog);
                    //wListFiles.ShowDialog();
                    //numeroEscolhido.Text = wListFiles.SelectFile();
                    //numeroEscolhido.SelectAll();
                    break;
            }


        }
    }
}

ps:我从没有MVVM的版本导入的注释行

Main.xaml

<Window x:Name="wMain" x:Class="SLMT.Views.Main"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:SLMT.Views"
        mc:Ignorable="d"
        Title="Ministério Tons" Height="364" Width="700" ResizeMode="NoResize" WindowStartupLocation="CenterScreen" WindowStyle="None">
    <Grid x:Name="gMain" KeyUp="GMain_KeyUp">
        <Image x:Name="imgBackground" HorizontalAlignment="Left" Height="364" VerticalAlignment="Top" Width="700" Source="/SLMT;component/Resources/img/background2.jpg" Opacity="100"/>
        <TextBox x:Name="chosenNumber" HorizontalAlignment="Center" Height="34" Margin="500,294,56,36" TextWrapping="Wrap" VerticalAlignment="Center" Width="144" BorderBrush="{x:Null}" Background="{x:Null}" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" UndoLimit="50" ForceCursor="True" PreviewTextInput="ChosenNumber_PreviewTextInput" KeyUp="ChosenNumber_KeyUp" BorderThickness="0" FontSize="20" Opacity="0.6" FontWeight="Bold"/>

    </Grid>
</Window>

CatalogViewModel.cs的相关部分

namespace SLMT.ViewModel
{
    public class CatalogViewModel: ObservableCollection <File>
    {

        private int selectedNumber;

        /// <summary>
        /// Contain the selected number in the View
        /// </summary>
        public int SelectedNumber
        {
            get { return selectedNumber; }
            set { selectedNumber = value; }
        }


        // REMOVED CODE TO SCAN AND INDEX THE FILES



        public CatalogViewModel() : base()
        {
            ScanFiles();
            ValidateAndAddFiles();
            ShowAlerts();
        }





        public void OpenSelectedFile()
        {
            OpenFileByNumber(SelectedNumber);
        }


        /// <summary>
        /// Get the file from catalog identified my the number
        /// </summary>
        /// <param name="number"></param>
        /// <returns>File|null</returns>
        private File GetFileByNumber(int number)
        {
            foreach (var file in this)
            {
                if (file.number == number){
                    return file;
                }
            }

            return null;
        }

        private void OpenFileByNumber(int number)
        {
            var file = GetFileByNumber(number);

            if (file == null)
            {
                MessageBox.Show("Nenhum arquivo encontrado com o número: \'" + number +"\"",
                   "ARQUIVO NÃO ENCONTRADO", MessageBoxButton.OK, MessageBoxImage.Warning);
            } else
            {
                file.Open();
            }
        }                
    }
}

2 个答案:

答案 0 :(得分:1)

尝试以下操作以访问您在构造函数中创建的视图模型。

CatalogViewModel vm = new CatalogViewModel();

public Main()
{
    InitializeComponent();
    DataContext = vm;
    chosenNumber.Focus();
}

然后在你的密钥处理程序中,你可以这样做:

private void ChosenNumber_KeyUp(object sender, KeyEventArgs e)
{
    if (e.Key == Key.Enter && chosenNumber.Text != String.Empty)
    {
        //catalog.OpenFileByNumber(ConvertToInt(numeroEscolhido.Text));
        //catalog.OpenSelectedFile(); // Will become someting like this

        vm.OpenSelectedFile();
        chosenNumber.Text = String.Empty;            
    }
}

更新:使用ICommand

使用您从here指向的BaseCommand类。我将以下代码添加到CatalogViewModel并使其正常工作。您将BaseCommand中的类型作为视图模型,但这应该是命令参数的类型(基于该帖子中的示例)。

private BaseCommand<object> _selectFileCommand;
public ICommand SelectFileCommand
{
    get
    {
        if (_selectFileCommand == null)
        {
            _selectFileCommand = new BaseCommand<object>((commandParam) => OpenSelectedFile(commandParam),
                                                         (commandParam) => CanOpenSelectedFile(commandParam));
        }
        return _selectFileCommand;
    }
}

public void OpenSelectedFile(object commandParam = null)
{
    Debug.WriteLine("CatalogViewModel.OpenSelectedFile was called.");
    Debug.WriteLine("SelectedValue = " + this.SelectedValue);
}

public bool CanOpenSelectedFile(object commandParam = null)
{
    return true;
}

进入OpenSelectedFile方法后,您应该能够将其连接到您想要的功能。

答案 1 :(得分:1)

你需要在这里做一些事情。

  1. 对于问题1,您必须绑定将数据传递给ViewModel的View。 在这种情况下,TextBox.Text属性必须绑定到CatalogViewModel.SelectedNumber属性:

    <TextBox x:Name="chosenNumber" 
         ...
         Text={Binding SelectedNumber} />
    
  2. 对于MVVM,CatalogViewModel类必须实现INotifyPropertyChanged接口。只需为您拥有的ObservableCollection创建一个属性。

  3. 对于问题2,您需要使用KeyBinding和ICommand来完成这项工作。 在视图中,它应该如下所示:

    <TextBox x:Name="chosenNumber" 
     ...
     Text={Binding SelectedNumber}>
        <TextBox.InputBindings>    
            <KeyBinding Key="Enter"
                        Command="{Binding SelectFileCommand}" />
        </TextBox.InputBindings>
    </TextBox>
    
  4. 在ViewModel中,您需要一个ICommand属性:

    public class CatalogViewModel: INotifyPropertyChanged
    {
            private BaseCommand _selectFileCommand;
            public ICommand SelectFileCommand
            {
                get
                {
                    if (_selectFileCommand == null)
                    {
                        _selectFileCommand = new BaseCommand(SelectFile, CanSelectFile);
                    }
                    return _selectFileCommand;
                }
            }
    ...
    

    其中,SelectFile是一个将执行操作的函数,CanSelectFile是函数,告诉命令是否可以执行,BaseCommand是ICommand接口的一个实现。 您可以参考以下问题:WPF selectedItem on Menu or get commandparameter in viewmodel

    更新: 使用此BaseCommand实施,而不是BaseCommand<T>

    class BaseCommand : ICommand
    {
        private readonly Action _executeMethod = null;
        private readonly Func<bool> _canExecuteMethod = null;
    
        public BaseCommand(Action executeMethod, Func<bool> canExecuteMethod)
        {
            _executeMethod = executeMethod;
            _canExecuteMethod = canExecuteMethod;
        }
    
        public event EventHandler CanExecuteChanged;
    
        public bool CanExecute()
        {
            if (_canExecuteMethod != null)
            {
                return _canExecuteMethod();
            }
            return true;
        }
    
        public void Execute()
        {
            if (_executeMethod != null)
            {
                _executeMethod();
            }
        }
    }
    

    试一试,让我们知道会发生什么。