将用户控件的SelectedValue绑定到ViewModel

3
在我的解决方案中,有两个项目:一个是WPF UserControl Library,另一个是WPF Application。
用户控件很简单,它只包含一个标签和一个下拉框,用于显示已安装的打印机。
在WPF应用程序中,我想使用这个用户控件。所选择的值将存储在用户设置中。
我的问题在于,我无法似乎无法得到正确的绑定工作方式。我需要发生的是,在MainWindow加载时能够设置UserControl的SelectedValue;以及在保存设置时访问UserControl的SelectedValue。
以下是我的代码,请问有谁能指点我一下正确的方向吗?
PrintQueue用户控件:
<UserControl x:Class="WpfControls.PrintQueue"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:wpfControls="clr-namespace:WpfControls"
             mc:Ignorable="d">
    <UserControl.DataContext>
        <wpfControls:PrintQueueViewModel/>
    </UserControl.DataContext>
    <Grid>
        <StackPanel Orientation="Horizontal">
            <Label Content="Selected Printer:"></Label>
            <ComboBox ItemsSource="{Binding Path=PrintQueues, Mode=OneWay}" DisplayMemberPath="Name" SelectedValuePath="Name" Width="200" SelectedValue="{Binding Path=SelectedPrinterName, Mode=TwoWay}"></ComboBox>
        </StackPanel>
    </Grid>
</UserControl>

打印队列代码后端:

public partial class PrintQueue : UserControl
{
    public static readonly DependencyProperty CurrentPrinterNameProperty =
        DependencyProperty.Register("CurrentPrinterName", typeof (string), typeof (PrintQueue), new PropertyMetadata(default(string)));

    public string CurrentPrinterName
    {
        get { return (DataContext as PrintQueueViewModel).SelectedPrinterName; }
        set { (DataContext as PrintQueueViewModel).SelectedPrinterName = value; }
    }


    public PrintQueue()
    {
        InitializeComponent();
        DataContext = new PrintQueueViewModel();
    }
}

打印队列视图模型:

public class PrintQueueViewModel : ViewModelBase
{
    private ObservableCollection<System.Printing.PrintQueue> printQueues;
    public ObservableCollection<System.Printing.PrintQueue> PrintQueues
    {
        get { return printQueues; }
        set
        {
            printQueues = value;
            NotifyPropertyChanged(() => PrintQueues);
        }
    }


    private string selectedPrinterName;
    public string SelectedPrinterName
    {
        get { return selectedPrinterName; }
        set
        {
            selectedPrinterName = value;
            NotifyPropertyChanged(() => SelectedPrinterName);
        }
    }

    public PrintQueueViewModel()
    {
        PrintQueues = GetPrintQueues();
    }


    private static ObservableCollection<System.Printing.PrintQueue> GetPrintQueues()
    {
        var ps = new PrintServer();
        return new ObservableCollection<System.Printing.PrintQueue>(ps.GetPrintQueues(new[]
            {
                EnumeratedPrintQueueTypes.Local,
                EnumeratedPrintQueueTypes.Connections
            }));
    }
}

主窗口:

<Window x:Class="WPFApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:wpfControls="clr-namespace:WpfControls;assembly=WpfControls" xmlns:wpfApp="clr-namespace:WPFApp"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <wpfApp:MainWindowViewModel/>
    </Window.DataContext>
    <Grid>
        <StackPanel>
            <wpfControls:PrintQueue CurrentPrinterName="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.PrinterName, Mode=TwoWay}"></wpfControls:PrintQueue>
        </StackPanel>
    </Grid>
</Window>

主窗口视图模型:

public class MainWindowViewModel : ViewModelBase
{
    private string printerName;

    public string PrinterName
    {
        get { return printerName; }
        set
        {
            printerName = value;
            NotifyPropertyChanged(() => PrinterName);
        }
    }

    public MainWindowViewModel()
    {
        PrinterName = "Lexmark T656 PS3";
    }
}
3个回答

6

库中的控件需要公开DependencyProperties,以便您可以在视图中进行绑定。就像WPF的TextBox公开Text属性一样。

您的PrintQueue控件没有公开任何内容,而是将其所有状态保存在无法访问的视图模型中。您的MainWindowViewModel无法获取PrintQueueViewModel内部的内容。

您需要在PrintQueue xaml的代码后台中公开SelectedPrinterName作为DependencyProperty。然后在MainWindow.xaml中,您可以将其绑定到MainWindowViewModel.PrinterName

如果您想一直使用ViewModels,那么MainWindowViewModel应该自己创建PrintQueueViewModel,以便可以访问其中的属性。

根据您的更新/评论:

不幸的是,DependencyProperties并不像那样工作。大多数情况下甚至不使用getter/setter,它们应该仅更新属性本身。您现在处于两个世界之间。

如果我处于您的位置,并且假设您可以更改库,使PrintQueue.xaml在视图中没有硬编码的VM实例,我将自己创建PrintQueueViewModel。这就是MVVM的工作方式:

ViewModel:

public class MainWindowViewModel : ViewModelBase
{
    public PrintQueueViewModel PrintQueue { get; private set; }

    public MainWindowViewModel()
    {
        PrintQueue = new PrintQueueViewModel();
        PrintQueue.SelectedPrinterName = "Lexmark T656 PS3";
    }
}

视图:

<Window x:Class="WPFApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:wpfControls="clr-namespace:WpfControls;assembly=WpfControls" xmlns:wpfApp="clr-namespace:WPFApp"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <wpfApp:MainWindowViewModel/>
    </Window.DataContext>
    <Grid>
        <StackPanel>
            <wpfControls:PrintQueue DataContext="{Binding PrintQueue}"/>
        </StackPanel>
    </Grid>
</Window>

然而,控件库通常没有视图模型,它们通过依赖属性公开其状态,因为它们被设计用于XAML。

组件库可能会公开视图模型,但在这种情况下,它们不会在视图中硬编码视图模型。

您写了这个库吗?如果不是,作者希望人们如何使用它?


实际上,它就像PrintQueue.DataContext as PrintQueueViewModel一样简单,但我同意在UserControl上添加一个类型属性更好。我不知道ComboBox的SelectedValue绑定是否会按原样工作。 - Dtex
@Gaz:我按照你的建议刚刚更新了代码,但还是没有成功。我没有遇到任何绑定错误,并且在CurrentPrinterName的setter上设置断点后发现它甚至没有被触发。我是不是漏掉了什么愚蠢的东西? - Jim B

0

我想,通过这些小改变,一切都应该能够正常运行

<ComboBox ItemsSource="{Binding Path=PrintQueues, Mode=OneWay}" DisplayMemberPath="Name" Width="200" SelectedItem="{Binding Path=SelectedPrinter, Mode=TwoWay}"></ComboBox>


private System.Printing.PrintQueue selectedPrinter;
public System.Printing.PrintQueue SelectedPrinter
{
    get { return selectedPrinter; }
    set
    {
        selectedPrinter = value;
        NotifyPropertyChanged(() => SelectedPrinter);
    }
}

现在你可以从主窗口修改视图模型上的SelectedPrinter,这个变化应该会反映在视图上。

(PrintQueue.DataContext as PrintQueueViewModel).SelectedPrinter = ...

0

我尝试了你的代码,以及将PrintQueueView绑定到相应的视图模型上,一切都很好。你的问题在于MainWindowViewModel不知道PrintQueueViewModel,因此无法在主窗口关闭时检索所选打印机的值(我猜这是你想要实现的场景)。

解决你的问题最快的方法是按照以下步骤进行:

  1. 在MainWindow.xaml中,给PrintQueue一个Name,这样你就可以在代码后台中访问它
  2. 在MainWindow.xaml.cs中,重写OnClosing方法。在其中,你可以按照以下方式检索视图模型:var viewModel = (PrintQueueViewModel)PrintQueue.DataContext;。之后,你可以检索所选值并保存它或执行其他操作。
  3. MainWindow构造函数中,在InitializeComponent之后,你可以从文件中检索你保存的值,并通过与前面步骤相同的方式检索并设置PrintQueueViewModel

MainWindow.xaml.cs中的整个代码:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        // Retrieve your selected printer here; in this case, I just set it directly
        var selectedPrinter = "Lexmark T656 PS3";
        var viewModel = (PrintQueueViewModel)PrintQueue.DataContext;
        viewModel.SelectedPrinterName = selectedPrinter;
    }
    protected override void OnClosing(CancelEventArgs e)
    {
        var viewModel = (PrintQueueViewModel)PrintQueue.DataContext;
        var selectedPrinterName = viewModel.SelectedPrinterName;
        // Save the name of the selected printer here
        base.OnClosing(e);
    }
}

请记住,视图模型的主要观点是能够对GUI逻辑进行单元测试并断开GUI外观和逻辑之间的联系。您的视图模型不应该能够检索系统中所有可能的打印机,而应该通过依赖注入等方式获取这些值。我建议您阅读有关SOLID编程的内容。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接