将UserControl的SelectedValue绑定到ViewModel

时间:2014-02-26 14:36:05

标签: c# wpf xaml user-controls

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

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

在WPF应用程序中;我想使用这个usercontrol。所选值将存储在用户设置中。

我遇到的问题是我似乎无法获得正确的绑定工作。我需要做的是能够在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 View Model:

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 个答案:

答案 0 :(得分:6)

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

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

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

如果您希望一直使用ViewModel,那么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中使用。

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

你写过这个图书馆吗?如果没有,作者是如何期望人们使用它的?

答案 1 :(得分: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);
    }
}

现在从主窗口可以修改viewmodel上的SelectedPrinter,并且更改应该反映在视图上

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

答案 2 :(得分:0)

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

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

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

    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编程的内容。