【问题标题】:MVVM Navigating through different ViewsMVVM 浏览不同的视图
【发布时间】:2016-04-11 11:46:03
【问题描述】:

最近几天我一直在阅读并尝试从该页面应用导航模式:https://rachel53461.wordpress.com/2011/12/18/navigation-with-mvvm-2/

现在,在我的项目开始工作后,我对绑定在这里的工作方式感到非常困惑。首先,我必须澄清我不想要一个像给定示例中那样始终可见的导航窗格。我只想使用我的 MainView 进行导航,每个“子视图”应该只能返回到它的“父视图”。

这是我得到的:

项目:APP

类:App.xaml.cs

protected override void OnStartup(StartupEventArgs e) {
        base.OnStartup(e);
        UI.View.Main.MainView app = new UI.View.Main.MainView();
        UI.View.Main.MainViewModel viewModel = new UI.View.Main.MainViewModel(some dependencies);
        app.DataContext = viewModel;
        app.Show();
    }

ViewModel 基类

public abstract class BaseViewModel : INotifyPropertyChanged {
    public event PropertyChangedEventHandler PropertyChanged;
    private string _name;
    public string Name {
        get {
            return _name;
        }
        set {
            if (Name != value) {
                _name = value;
                OnPropertyChanged("Name");
            }
        }
    }
    private BaseViewModel _homePage;
    public BaseViewModel HomePage {
        get {
            return _homePage;
        }
        set {
            if (HomePage != value) {
                _homePage = value;
                OnPropertyChanged("HomePage");
            }
        }
    }
    public void OnPropertyChanged(string propertyName) {
        PropertyChangedEventHandler temp = PropertyChanged;
        if (temp != null) {
            temp(this, new PropertyChangedEventArgs(propertyName));
        }
    }

}

主视图模型

namespace SGDB.UI.View.Main {
    public class MainViewModel : BaseViewModel {


private BaseViewModel _currentPageViewModel;
        public BaseViewModel CurrentPageViewModel {
            get {
                return _currentPageViewModel;
            }
            set {
                if (CurrentPageViewModel != value) {
                    _currentPageViewModel = value;
                    OnPropertyChanged("CurrentPageViewModel");
                }
            }
        }
        public List<BaseViewModel> PageViewModels { get; private set; }
        public RelayCommand ChangePageCommand {
            get {
                return new RelayCommand(p => ChangeViewModel((BaseViewModel)p), p => p is BaseViewModel);
            }
        }

        //Some Dependencies

        public List<BaseViewModel> ViewPages { get; private set; }

        public MainViewModel(some dependencies) {
            HomePage = new HomeViewModel() { Name = "TEST" };
            //assign dependencies
            var uavm = new UserAdministration.UserAdministrationViewModel(_userUnitOfWork, _personUnitOfWork) {
                Name = Resources.Language.Sys.UserAdministartionTitle
            };
            PageViewModels = new List<BaseViewModel>();
            PageViewModels.Add(uavm);
            ChangeViewModel(HomePage);
        }


        public void ChangeViewModel(BaseViewModel viewModel) {
            CurrentPageViewModel = viewModel;
        }
    }
}

主视图

<Window x:Class="SGDB.UI.View.Main.MainView"
    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:SGDB.UI.View.Main"
    xmlns:ua="clr-namespace:SGDB.UI.View.UserAdministration"
    xmlns:home="clr-namespace:SGDB.UI.View.Home"
    mc:Ignorable="d"
    Title="MainView" Height="400" Width="800">
<Window.Resources>
    <DataTemplate DataType="{x:Type home:HomeViewModel}">
        <home:Home/>
    </DataTemplate>
    <DataTemplate DataType="{x:Type ua:UserAdministrationViewModel}">
        <ua:UserAdministration/>
    </DataTemplate>
</Window.Resources>
<ContentControl Content="{Binding CurrentPageViewModel}"/>

主视图模型

public class HomeViewModel : BaseViewModel {
    public RelayCommand TestCommand {
        get {
            return new RelayCommand((x) => MessageBox.Show(x.ToString()), (x) => true);
        }
    }
}

主视图

<UserControl x:Class="SGDB.UI.View.Home.Home"
    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:SGDB.UI.View.Home"
    xmlns:controls="clr-namespace:SGDB.UI.Controls"
    xmlns:resx="clr-namespace:SGDB.UI.Resources.Language"
    mc:Ignorable="d"
    d:DesignHeight="400" d:DesignWidth="800">
<Grid>
    <Grid.Resources>
        <Style TargetType="controls:ModernButton">
            <Setter Property="Margin" Value="1"/>
            <Setter Property="FontFamily" Value="Bosch Office Sans"/>
            <Setter Property="FontWeight" Value="Bold"/>
            <Setter Property="Size" Value="155"/>
        </Style>
    </Grid.Resources>
    <Grid.Background>
        <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
            <LinearGradientBrush.GradientStops>
                <GradientStop Color="#26688B" Offset="1"/>
                <GradientStop Color="#11354C" Offset="0"/>
            </LinearGradientBrush.GradientStops>
        </LinearGradientBrush>
    </Grid.Background>
    <Grid.RowDefinitions>
        <RowDefinition Height="60"/>
        <RowDefinition Height="3*"/>
        <RowDefinition Height="2*"/>
        <RowDefinition Height="25"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="150"/>
    </Grid.ColumnDefinitions>
    <StackPanel Grid.Row="0" Grid.Column="0" HorizontalAlignment="Center">
        <StackPanel.Resources>
            <Style TargetType="TextBlock">
                <Setter Property="Foreground" Value="White"/>
                <Setter Property="FontFamily" Value="Bosch Office Sans"/>
            </Style>
        </StackPanel.Resources>
        <TextBlock Text="{x:Static resx:Sys.ApplicationTitle}"  FontSize="20" FontWeight="Bold" Margin="5"/>
        <TextBlock Text="{x:Static resx:Sys.ApplicationSubTitle}"  FontSize="12" FontWeight="Light"/>
    </StackPanel>
    <WrapPanel Grid.Row="1" 
               Grid.Column="0" 
               FlowDirection="LeftToRight" 
               HorizontalAlignment="Left" 
               Width="367">
        <ItemsControl ItemsSource="{Binding DataContext.PageViewModels, RelativeSource={RelativeSource AncestorType={x:Type Window}}}">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <controls:ModernButton Background="Dark" 
                               Text="{Binding Name}"
                                       Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
                                       CommandParameter="{Binding}"/>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
        <Button Content="{Binding Name}" Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
            CommandParameter="{Binding HomePage}"/>
// This Button is always disabled although HomePage is of Type HomeViewModel which is based on BaseViewModel.
    </WrapPanel>
</Grid>

我的问题是:

  1. 为什么HomeView会打结说HomeViewModel就是它的ViewModel?我没有在代码中的任何地方定义它。
  2. 为什么对名称属性的绑定有效,但对主页属性的绑定无效?它们都在 BaseViewModel 类中定义。

更新 1:

RelayCommand 类:

public class RelayCommand : ICommand {
    public event EventHandler CanExecuteChanged;

    readonly Action<object> _action;
    readonly Predicate<object> _predicate;
    public RelayCommand(Action<object> action, Predicate<object> predicate) {
        _action = action;
        _predicate = predicate;
    }
    public RelayCommand(Action<object> action) {
        _action = action;
        _predicate = ((x) => true);
    }

    public bool CanExecute(object parameter) {
        return _predicate(parameter);
    }

    public void Execute(object parameter) {
        _action(parameter);
    }
}

更新 2:

真正的问题是什么?

<Button Content="{Binding Name}" Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
            CommandParameter="{Binding HomePage}"/>

内容已正确绑定,但应为 BaseViewModel 类型的 CommandParameter (HomePage) 不会通过 Command 的 CanExecute 进行验证。属性、名称和主页都在 BaseViewModel 中定义。

更新 3:

<Button Content="{Binding Name}" Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
        CommandParameter="{Binding DataContext.HomePage, ElementName=Test}"/>

【问题讨论】:

  • 检查调试输出窗口是否有任何绑定警告或错误。这可能会澄清发生了什么。
  • 不幸的是,调试窗口没有说明任何绑定错误(让我想知道)。

标签: c# wpf xaml mvvm


【解决方案1】:
  1. 在你有下一行:

    <DataTemplate DataType="{x:Type home:HomeViewModel}">
            <home:Home/>
    </DataTemplate> 
    

表示HomeViewModel的视觉形式是Home。

  1. 您的绑定工作正常,我认为您的问题是命令本身。我不知道什么是 RelayCommand 但我认为你的错误来自那里。 RelayCommand 应该是这样的:

    public abstract class BaseViewModel : INotifyPropertyChanged 
    {
        private ICommand _f1KeyCommand;
    
        public ICommand F1KeyCommand
        {
            get
            {
                if (_f1KeyCommand == null)
                    _f1KeyCommand = new DelegateCommand(F1KeyCommandCallback, CanExecute);
    
                return _f1KeyCommand;
            }
        }
    
        /// <summary>
        /// Fired if F1 is pressend and 'CanExecute' returns true
        /// </summary>
        private void F1KeyCommandCallback(object obj)
        {
            Console.WriteLine("F1KeyCommandCallback fired");
        }
    
        // ....
    }
    

该类允许将命令逻辑委托给作为参数传递的方法,并使视图能够将命令绑定到不属于元素树的对象:

public class DelegateCommand : ICommand
{
    #region Data Members

    private Action<object> execute;
    private Predicate<object> canExecute;
    private event EventHandler CanExecuteChangedInternal;

    #endregion

    #region Ctor

    public DelegateCommand(Action<object> execute)
       : this(execute, DefaultCanExecute)
    {
    }

    public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
    {
       if (execute == null)
       {
           throw new ArgumentNullException("execute");
       }

       if (canExecute == null)
       {
           throw new ArgumentNullException("canExecute");
       }

       this.execute = execute;
       this.canExecute = canExecute;
    }


    #endregion

    #region Properties

    public event EventHandler CanExecuteChanged
    {
        add
        {
            CommandManager.RequerySuggested += value;
            this.CanExecuteChangedInternal += value;
        }

        remove
        {
            CommandManager.RequerySuggested -= value;
            this.CanExecuteChangedInternal -= value;
        }
    }

    #endregion

    #region Public Methods

    public bool CanExecute(object parameter)
    {
        return this.canExecute != null && this.canExecute(parameter);
    }

    public void Execute(object parameter)
    {
        this.execute(parameter);
    }

    public void OnCanExecuteChanged()
    {
        EventHandler handler = this.CanExecuteChangedInternal;
        if (handler != null)
        {
            handler.Invoke(this, EventArgs.Empty);
        }
    }

    public void Destroy()
    {
        this.canExecute = _ => false;
        this.execute = _ => { return; };
    }

    #endregion

    #region Private Methods

    private static bool DefaultCanExecute(object parameter)
    {
        return true;
    }

    #endregion
}

在你看来:

 <controls:ModernButton Background="Dark" 
                       Text="{Binding Name}"
                       Command="{Binding F1KeyCommand"
                       CommandParameter="{Binding}"/>

【讨论】:

  • 我已将 CanExecute 方法更改为仅返回 true - 按钮已启用,单击它会导航回 HomeView。但是为什么不能正确验证我的 CanExecute 方法呢? (p => p 是 BaseViewModel)。此方法在其他方法上按预期工作...
  • 如果你读到了这篇文章,你能解释一下为什么我需要为我的主页属性引用 ElementName 而不是我的 Name 属性,即使两者都位于同一个类中? (见更新的问题)
猜你喜欢
  • 2014-05-12
  • 1970-01-01
  • 2016-02-16
  • 1970-01-01
  • 1970-01-01
  • 2019-08-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多