在更改组合框时填充 ListView - 正确的 MVVM 实现
Populate ListView upon changing combobox - proper MVVM implementation
我的目标是:包含 Jira 帐户名列表的 ComboxBox 和 ListView,它将显示在 ComboBox 中选择用户后发送到 jira 的查询的响应(因为帐户名是部分查询)。
我所拥有的: 对 C#、WPF、MVVM 和工作解决方案知之甚少(代码如下),但它不是 MVVM反正。所以,我已经阅读了很多关于 MVVM(relayCommand、PropertyChanged 等)的文章,但出于某种原因,我无法想出如何将此程序重构为 MVVM 的解决方案。最大的问题之一是我无法弄清楚如何向 Jira 发出该请求并导致 IQueryable 的形式适合 MVVM 模式。我的意思是,我应该把它放在哪里。
所以, 如果有人可以提示我一般应该怎么做才能将此程序转换为遵循 MVVM 模式或任何其他类型的建议,我将非常感激!
MainWindow.xamls.cs
public ObservableCollection<Issue> Issues { get; set; }
private void OnNameComboChanged(object sender, EventArgs e)
{
Issues.Clear();
string name = ((sender as ComboBox).SelectedItem as ComboBoxItem).Content as string;
Issues fetchedIssues = new Issues();
var issuesList = fetchedIssues.FetchIssues(name); // returns the list of Issues in a type of --> IQueryable<Issue>
foreach (var issue in issuesList)
{
Issues.Add(issue);
}
}
public MainWindow()
{
Issues = new ObservableCollection<Issue>();
InitializeComponent();
}
MainWindow.xaml
<Controls:MetroWindow x:Name="Main_Window" x:Class="Dull.MainWindow"
........
DataContext="{Binding RelativeSource={RelativeSource Self}}"> <!-- how I link contexts-->
<Controls:MetroWindow.RightWindowCommands>
<Controls:WindowCommands>
<ComboBox x:Name="Name" SelectionChanged="OnNameComboChanged" > <!-- Combox box with nicknames -->
<ComboBoxItem>name of the user</ComboBoxItem>
<ComboBoxItem>another name of the user</ComboBoxItem>
</ComboBox>
</Controls:WindowCommands>
</Controls:MetroWindow.RightWindowCommands>
<Grid>
<ListView x:Name="issuesListView" ItemsSource="{Binding Issues}"> <!-- ListView binded to Issues collection -->
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Summary}"
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
有各种框架,如 Prism, Caliburn Micro, MVVMLight 等,它们提供了编写 MVVM 设计模式应用程序的功能。上述框架提供的功能很少
- DelegateCommand 或 RelayCommand
- ViewModelLocator
- Container/Module
- 事件聚合器
这些特性使在 MVVM 设计模式中编写代码变得容易。但是,如果您不需要所有这些功能并且不想集成这些功能,那么请不要担心。
现在,这个答案中的所有对话都是基于您想要在没有这些框架的情况下为您的实现编写的。
可以参考这篇博客写RelayCommand. You do require ICommand
implementation if you want to segregate View from ViewModel. These commands of ViewModel can be integrated with View using Blends' Interactivity trigger (refer this sample).
以上是为解决您的问题所做的准备工作。按照步骤
- 创建视图模型
下面的 ViewModel 描述了您的需求:
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<Issue> issues = new ObservableCollection<Issue>();
public ObservableCollection<Issue> Issues { get {return issues;} }
private ObservableCollection<string> users = new ObservableCollection<string>();
public ObservableCollection<string> Users { get {return users;} }
private string user;
public string User
{
get
{
return user;
}
set
{
user = value;
NotifyPropertyChanged();
}
}
private ICommand userChangedCommand;
public ICommand UserChangedCommand
{
get
{
return userChangedCommand ?? (userChangedCommand = new RelayCommand(
x =>
{
OnUserChanged();
}));
}
}
private ICommand loadedCommand;
public ICommand LoadedCommand
{
get
{
return loadedCommand?? (loadedCommand= new RelayCommand(
x =>
{
// Write Code here to populate Users collection.
}));
}
}
private void OnUserChanged()
{
Issues.Clear();
string name = this.User;
Issues fetchedIssues = new Issues();
var issuesList = fetchedIssues.FetchIssues(name); // returns the list of Issues in a type of --> IQueryable<Issue>
foreach (var issue in issuesList)
{
Issues.Add(issue);
}
}
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
2。查看更改:
<Controls:MetroWindow x:Name="Main_Window" x:Class="Dull.MainWindow" ........
>
<i:EventTrigger EventName="Loaded" >
<i:InvokeCommandAction Command="{Binding LoadedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<Controls:MetroWindow.RightWindowCommands>
<Controls:WindowCommands>
<ComboBox x:Name="Name" ItemsSource="{Binding Users}" SelectionChanged="OnNameComboChanged" SelectedItem="{Binding User}" > <!-- Combox box is getting user details from ViewModel -->
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged" >
<i:InvokeCommandAction Command="{Binding UserChangedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</Controls:WindowCommands>
</Controls:MetroWindow.RightWindowCommands>
<Grid>
<ListView x:Name="issuesListView" ItemsSource="{Binding Issues}"> <!-- ListView binded to Issues collection -->
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Summary}"
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
3. 现在是最后一部分如何将 ViewModel 绑定到视图.
如果您使用上述框架,那么基于 ViewModelLocator
功能,这将是微不足道的。但是,要在没有框架的情况下实现,您可以使用以下方法之一。
1)创建实例ViewModel并在Control的InitializeComponent方法中赋值(.Xaml.cs)
var vm = new MyViewModel();
this.DataContext = vm;
然而这打破了纯 MVVM 设计模式
2) 您可以在视图本身中创建实例
<Controls:MetroWindow x:Name="Main_Window" x:Class="Dull.MainWindow">
<Controls:MetroWindow.DataContext>
<VM:MyViewModel />
</Controls:MetroWindow.DataContext>
...............
</Controls:MetroWindow>
我的目标是:包含 Jira 帐户名列表的 ComboxBox 和 ListView,它将显示在 ComboBox 中选择用户后发送到 jira 的查询的响应(因为帐户名是部分查询)。
我所拥有的: 对 C#、WPF、MVVM 和工作解决方案知之甚少(代码如下),但它不是 MVVM反正。所以,我已经阅读了很多关于 MVVM(relayCommand、PropertyChanged 等)的文章,但出于某种原因,我无法想出如何将此程序重构为 MVVM 的解决方案。最大的问题之一是我无法弄清楚如何向 Jira 发出该请求并导致 IQueryable 的形式适合 MVVM 模式。我的意思是,我应该把它放在哪里。
所以, 如果有人可以提示我一般应该怎么做才能将此程序转换为遵循 MVVM 模式或任何其他类型的建议,我将非常感激!
MainWindow.xamls.cs
public ObservableCollection<Issue> Issues { get; set; }
private void OnNameComboChanged(object sender, EventArgs e)
{
Issues.Clear();
string name = ((sender as ComboBox).SelectedItem as ComboBoxItem).Content as string;
Issues fetchedIssues = new Issues();
var issuesList = fetchedIssues.FetchIssues(name); // returns the list of Issues in a type of --> IQueryable<Issue>
foreach (var issue in issuesList)
{
Issues.Add(issue);
}
}
public MainWindow()
{
Issues = new ObservableCollection<Issue>();
InitializeComponent();
}
MainWindow.xaml
<Controls:MetroWindow x:Name="Main_Window" x:Class="Dull.MainWindow"
........
DataContext="{Binding RelativeSource={RelativeSource Self}}"> <!-- how I link contexts-->
<Controls:MetroWindow.RightWindowCommands>
<Controls:WindowCommands>
<ComboBox x:Name="Name" SelectionChanged="OnNameComboChanged" > <!-- Combox box with nicknames -->
<ComboBoxItem>name of the user</ComboBoxItem>
<ComboBoxItem>another name of the user</ComboBoxItem>
</ComboBox>
</Controls:WindowCommands>
</Controls:MetroWindow.RightWindowCommands>
<Grid>
<ListView x:Name="issuesListView" ItemsSource="{Binding Issues}"> <!-- ListView binded to Issues collection -->
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Summary}"
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
有各种框架,如 Prism, Caliburn Micro, MVVMLight 等,它们提供了编写 MVVM 设计模式应用程序的功能。上述框架提供的功能很少
- DelegateCommand 或 RelayCommand
- ViewModelLocator
- Container/Module
- 事件聚合器
这些特性使在 MVVM 设计模式中编写代码变得容易。但是,如果您不需要所有这些功能并且不想集成这些功能,那么请不要担心。
现在,这个答案中的所有对话都是基于您想要在没有这些框架的情况下为您的实现编写的。
可以参考这篇博客写RelayCommand. You do require ICommand
implementation if you want to segregate View from ViewModel. These commands of ViewModel can be integrated with View using Blends' Interactivity trigger (refer this sample).
以上是为解决您的问题所做的准备工作。按照步骤
- 创建视图模型
下面的 ViewModel 描述了您的需求:
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<Issue> issues = new ObservableCollection<Issue>();
public ObservableCollection<Issue> Issues { get {return issues;} }
private ObservableCollection<string> users = new ObservableCollection<string>();
public ObservableCollection<string> Users { get {return users;} }
private string user;
public string User
{
get
{
return user;
}
set
{
user = value;
NotifyPropertyChanged();
}
}
private ICommand userChangedCommand;
public ICommand UserChangedCommand
{
get
{
return userChangedCommand ?? (userChangedCommand = new RelayCommand(
x =>
{
OnUserChanged();
}));
}
}
private ICommand loadedCommand;
public ICommand LoadedCommand
{
get
{
return loadedCommand?? (loadedCommand= new RelayCommand(
x =>
{
// Write Code here to populate Users collection.
}));
}
}
private void OnUserChanged()
{
Issues.Clear();
string name = this.User;
Issues fetchedIssues = new Issues();
var issuesList = fetchedIssues.FetchIssues(name); // returns the list of Issues in a type of --> IQueryable<Issue>
foreach (var issue in issuesList)
{
Issues.Add(issue);
}
}
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
2。查看更改:
<Controls:MetroWindow x:Name="Main_Window" x:Class="Dull.MainWindow" ........
>
<i:EventTrigger EventName="Loaded" >
<i:InvokeCommandAction Command="{Binding LoadedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<Controls:MetroWindow.RightWindowCommands>
<Controls:WindowCommands>
<ComboBox x:Name="Name" ItemsSource="{Binding Users}" SelectionChanged="OnNameComboChanged" SelectedItem="{Binding User}" > <!-- Combox box is getting user details from ViewModel -->
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged" >
<i:InvokeCommandAction Command="{Binding UserChangedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</Controls:WindowCommands>
</Controls:MetroWindow.RightWindowCommands>
<Grid>
<ListView x:Name="issuesListView" ItemsSource="{Binding Issues}"> <!-- ListView binded to Issues collection -->
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Summary}"
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
3. 现在是最后一部分如何将 ViewModel 绑定到视图.
如果您使用上述框架,那么基于 ViewModelLocator
功能,这将是微不足道的。但是,要在没有框架的情况下实现,您可以使用以下方法之一。
1)创建实例ViewModel并在Control的InitializeComponent方法中赋值(.Xaml.cs)
var vm = new MyViewModel();
this.DataContext = vm;
然而这打破了纯 MVVM 设计模式
2) 您可以在视图本身中创建实例
<Controls:MetroWindow x:Name="Main_Window" x:Class="Dull.MainWindow">
<Controls:MetroWindow.DataContext>
<VM:MyViewModel />
</Controls:MetroWindow.DataContext>
...............
</Controls:MetroWindow>