如何在 MVVM 中的用户控件和主控件之间链接命令



我有一个用户控件,它有一个TextBox和一个Button,称为视图。TextBox采用索引值。

在我的Main视图上,我有一个列表视图,它将显示文件中的所有行。 一个ObservableCollection是绑定到这个。

我需要的是,当在TextBox中输入索引值并单击"查看Button"(在用户控件中(时,ListViewSelecedIndex(在 Main 中(应更改为索引值。

如何使用 MVVM 实现此目的?

另外,如果我做错了,请提供正确的方法来执行此操作。

这是我的用户控制代码:

XAML

<UserControl.DataContext>
<VM:IndexView_VM ></VM:IndexView_VM>
</UserControl.DataContext>
<Grid Background="White">
<TextBlock Margin="10,12,168,9" Text="Index : "/>
<TextBox Text="{Binding Index}" x:Name="TB_Index" Margin="53,11,90,8" />
<Button Command="{Binding View_CMD}" x:Name="BT_View" Content="View" Margin="136,11,10,8" />
</Grid>

视图模型

public class IndexView_VM : ViewModelBase
{
public IndexView_VM()
{
View_CMD = new RelayCommand(_View_CMD);
}
int _Index;
public int Index
{
get { return _Index; }
set
{
_Index = value;
RaisePropertyChanged();
}
}
public RelayCommand View_CMD { get; set; }
internal void _View_CMD(object Parameter)
{
// What to write here?
}
}

以下是主要视图:

XAML

<UserControl.DataContext>
<VM:MainView_VM></VM:MainView_VM>
</UserControl.DataContext>
<Grid Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="111*"/>
<ColumnDefinition Width="100*"/>
</Grid.ColumnDefinitions>
<StackPanel>
<local:IndexView/>
<local:IndexView/>
<local:IndexView/>
<local:IndexView/>
</StackPanel>
<ListView ItemsSource="{Binding FileData}" x:Name="listView" Grid.Column="1" >
<ListView.View>
<GridView>
<GridViewColumn Header="Data" Width="100"/>
</GridView>
</ListView.View>
</ListView>
</Grid>

查看模型

public class MainView_VM : ViewModelBase
{
public MainView_VM()
{
ReadFile();
}
public ObservableCollection<string> FileData { get; set; }
void ReadFile()
{
//I will read file here.
}
}

下面是您想要执行的操作的一个非常基本的示例:

窗口 XAML

<Window.Resources>
<local:StringToIntConverter x:Key="StringToIntConverter" />
</Window.Resources>
<StackPanel>
<TextBlock>Select Index</TextBlock>
<TextBox x:Name="theTextBox" Text="{Binding SelectedIndex,ElementName=theList,Converter={StaticResource StringToIntConverter},Mode=OneWayToSource,UpdateSourceTrigger=Explicit}" />
<Button Click="Button_Click">Select Now</Button>
<ListBox x:Name="theList">
<ListBoxItem>First</ListBoxItem>
<ListBoxItem>Second</ListBoxItem>
<ListBoxItem>Third</ListBoxItem>
</ListBox>
</StackPanel>

窗口代码隐藏

private void Button_Click(object sender, RoutedEventArgs e)
{
theTextBox.GetBindingExpression(TextBox.TextProperty)?.UpdateSource();
}

解释

您要求通过单击按钮在文本框中的列表上设置索引。我们使用几个设置将文本框绑定到选定的索引:

  • 来源:精选索引
  • 元素名称:列表/目标 UI 元素
  • 转换器:需要从字符串到Int(我自己写的(
  • 模式:OneWayToSource,我们强制文本框仅将值发送到列表,而不是相反
  • UpdateSourceTrigger:我们不希望绑定自动更新,我们希望自己执行此操作

为了更新绑定,我们使用按钮的 Click 事件。

但是视图模型呢?该操作是仅查看操作,ViewModel 不需要知道任何有关它的信息,因此我们应该将其排除在操作之外。这就是我不使用命令绑定的原因。

哎呀,忘记了UserControl如果你想把它放在用户控件中,那么我建议你根本不创建 ViewModel。此外,在用户控件中,您不需要数据绑定,只需要在外部。保持简单:

用户控件 XAML

<TextBlock>Select Index</TextBlock>
<TextBox x:Name="theTextBox" />
<Button Click="Button_Click">Select Now</Button>

用户控件代码隐藏

public int Index
{
get { return (int)GetValue(IndexProperty); }
set { SetValue(IndexProperty, value); }
}
public static readonly DependencyProperty IndexProperty =
DependencyProperty.Register("Index", typeof(int), typeof(ListViewIndexSelectorControl), new PropertyMetadata(0));
private void Button_Click(object sender, RoutedEventArgs e)
{
if(int.TryParse(theTextBox.Text, out int result))
{
Index = result;
}
}

主窗口用法 XAML

<local:ListViewIndexSelectorControl Index="{Binding SelectedIndex,ElementName=theList,Mode=OneWayToSource}" />
<ListBox x:Name="theList">
<ListBoxItem>First</ListBoxItem>
<ListBoxItem>Second</ListBoxItem>
<ListBoxItem>Third</ListBoxItem>
</ListBox>

如果以后需要 ViewModel,还可以将视图用作简单控件的 ViewModel,只需在视图的构造函数中设置DataContext = this;,或者在 XAML 元素上使用名称,并通过 ElementName 绑定数据上下文。

如果您使用的是MvvmLight,例如,您可以使用Messenger类将消息从子视图模型发送到父视图模型:

IndexView_VM:

internal void _View_CMD(object Parameter)
{
GalaSoft.MvvmLight.Messaging.Messenger.Default.Send(_Index);
}

MainView_VM:

public class MainView_VM : ViewModelBase
{
public MainView_VM()
{
ReadFile();
GalaSoft.MvvmLight.Messaging.Messenger.Default.Register<int>(this, index => Index = index);
}
public ObservableCollection<string> FileData { get; set; }
int _Index;
public int Index
{
get { return _Index; }
set
{
_Index = value;
RaisePropertyChanged();
}
}
void ReadFile()
{
//I will read file here.
}
}

主视图:

<ListView ItemsSource="{Binding FileData}" x:Name="listView" Grid.Column="1"
SelectedIndex="{Binding Index}">
<ListView.View>
<GridView>
<GridViewColumn Header="Data" Width="100"/>
</GridView>
</ListView.View>
</ListView>

有关Messenger的详细信息,请参阅 Laurent Bugnion 的 MSDN 杂志文章。

MVVM - MVVM 中的 Messenger 和 View Services:https://msdn.microsoft.com/en-us/magazine/jj694937.aspx

不确定我是否理解正确。您想使用在该TextBox中键入的索引值来设置ListBox的选定项,对吗?

如果这是正确的,那么我有以下考虑:

  1. 在这种情况下,UserControl可能是矫枉过正。特别是如果您永远不会在 UI 中的其他地方重复使用它。
  2. 考虑到第一个考虑因素,您所要做的就是将该TextBox移动到您的主视图,为其命名并将.Text属性绑定到ListViewSelectedIndex属性。

例:

<ListView SelectedIndex="{Binding ElementName=txtIndex, Path=Text, Converter={StaticResource StringToIntConverter}, UpdateSourceTrigger=PropertyChanged}" />

需要该转换器(您需要实现(,因为将string转换为int可能不会自动发生。UpdateSourceTrigger=PropertyChanged将在您键入后立即更新目标属性,无需按钮。

在这一点上,你应该完成了。

如果你绝对需要这个UserControl,你可以添加一个DependencyProperty来公开TextBox的值。然后,您可以绑定到该属性,如上例所示。

最新更新