【问题标题】:Bind SelectedValue of UserControl to ViewModel将 UserControl 的 SelectedValue 绑定到 ViewModel
【发布时间】:2014-02-26 14:36:05
【问题描述】:

在我的解决方案中;我有两个项目:一个是 WPF 用户控件库,另一个是 WPF 应用程序。

用户控件非常简单;它是一个标签和一个组合框,将显示已安装的打印机。

在 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();
    }
}

PrintQueue 视图模型:

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";
    }
}

【问题讨论】:

    标签: c# wpf xaml user-controls


    【解决方案1】:

    库中的控件需要公开可以在视图中绑定的 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 应该如何工作的:

    视图模型:

    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 和 PrintQueueViewModel 一样简单,但我同意最好在 UserControl 上添加一个类型化的属性。我不知道 ComboBox 的 SelectedValue 绑定是否真的会按原样工作。
    • @Gaz;我刚刚根据您的建议更新了代码;但仍然没有运气。我没有抛出任何绑定错误;并在 CurrentPrinterName 的设置器上放置一个断点表明它甚至没有被击中。我错过了什么愚蠢的东西吗?
    【解决方案2】:

    我认为通过这些小改动,一切都会正常

    <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 = ...
    

    【讨论】:

      【解决方案3】:

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

      解决您的问题的最快方法是执行以下步骤:

      1. 在 MainWindow.xaml 中,将 PrintQueue 指定为 Name,以便您可以在后面的代码中访问它
      2. 在 MainWindow.xaml.cs 中,覆盖 OnClosing 方法。您可以在其中检索视图模型,如下所示:var viewModel = (PrintQueueViewModel)PrintQueue.DataContext;。之后,您可以检索选定的值并将其保存或其他任何方式。
      3. InitializeComponent 之后的MainWindow 构造函数中,您可以通过与上一步相同的方式从文件中检索保存的值并将其设置在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 编程的内容。

      【讨论】:

        猜你喜欢
        • 2018-10-29
        • 2016-07-11
        • 1970-01-01
        • 1970-01-01
        • 2011-07-19
        • 2011-10-31
        • 2019-11-27
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多