WPF同一窗口更改数据上下文和视图

3
我是WPF的新手,正在将一个应用程序从VC++ 6.0/MFC移植到c#/WPF(VS2013)。我的大部分Windows开发都是在VC++/MFC中进行的。我试图坚持MVVM模式,并编写了一些概念证明应用程序来熟悉它。到目前为止,我遇到了一个问题。
当我的应用程序启动时,它会呈现一个客户和账单的树形视图。我使用了一个简单的分层数据模板,每个级别都绑定到我的本地数据类型(视图模型)。我想要实现的是当选择一个账单时(现在我有一个在账单模板上按的按钮),我希望树形视图被替换为账单的详细视图(我不想弹出对话框)。 Startup Screen 此处的Xaml为:
   <DockPanel>
    <TreeView x:Name="trvGroups" ItemsSource="{Binding LBGroups}" VirtualizingPanel.IsVirtualizing="True" VirtualizingPanel.VirtualizationMode="Recycling">
    <TreeView.ItemContainerStyle>
        <!--
            This Style binds a TreeViewItem to a LBtreeViewItemViewModel
        -->
        <Style TargetType="{x:Type TreeViewItem}">
            <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
            <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
            <Setter Property="FontWeight" Value="Normal" />
        </Style>
    </TreeView.ItemContainerStyle>

    <TreeView.Resources>
        <HierarchicalDataTemplate
            DataType="{x:Type local:GroupViewModel}"
            ItemsSource="{Binding Children}"
            >
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding GroupName}" />
            </StackPanel>
        </HierarchicalDataTemplate>

        <HierarchicalDataTemplate 
            DataType="{x:Type local:BillViewModel}"
            ItemsSource="{Binding Children}">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding BillName}" />
                <Button Command="{Binding Path=BillEditCommand}">Edit</Button>
            </StackPanel>
        </HierarchicalDataTemplate>
    </TreeView.Resources>
    </TreeView>
</DockPanel>

目前我有更多的问题。我应该将每个视图定义为用户控件并将它们放在window.resources中吗?我使用数据模板吗?我假设我会更改数据上下文以指向详细账单视图模型。最好的方法是什么?

我的目标是遵循我理解的MVVM,即尽可能不在代码后面(或尽可能少)。

我更多地是在寻找指针,以使我开始沿着正确的道路进行研究。我现在有点困惑了。

提前致谢。


2
每个视图都应该是一个 UserControl(或 Window),并且将有自己的 XAML 文件和相应的(大多为空的)代码后备文件。您不应该在 Windows.Resources 中定义视图。但是,您可以在任何可以插入标准控件的地方插入 UserControl,并且可以显示 Window - Martin Liversage
1
业务逻辑放在视图模型中,UI相关内容放在代码后台中。MVVM != 无代码后台。 - user1228
在MVVM中阅读有关主/细节场景的内容。 - eran otzap
@Will 我认为 MVVM 的一个重要点是可测试性?你无法测试 Code-Behind。 - McGarnagle
是的,测试你的业务逻辑。这意味着不要在按钮事件处理程序中放置任何业务逻辑。测试UI则是另一回事。 - user1228
显示剩余2条评论
1个回答

1
我将为您提供一个简单的主从场景,您可以在TreeView中选择模型并编辑它们。
CS:
       public partial class MainWindow : Window , INotifyPropertyChanged
{
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = this;
    }

    private ICommand onEditBillCommand;
    public ICommand OnEditBillCommand
    {
        get
        {
            if (onEditBillCommand == null)
                onEditBillCommand = new RelayCommand<Bill>
                    (
                        bill => { CurrentBill = bill; }
                    );
            return onEditBillCommand;
        }
    }

    private Bill currectBill;
    public Bill CurrentBill
    {
        get { return currectBill; }
        set
        {
            currectBill = value;
            PropertyChanged(this, new PropertyChangedEventArgs("CurrentBill"));
        }
    }

    public List<Customer> Customers
    {
        get
        {
            List<Customer> customers = new List<Customer>();
            for (int i = 0; i < 5; i++)
            {
                customers.Add(CreateMockCustomer(i));
            }
            return customers;
        }
    }

    private Customer CreateMockCustomer(int g )
    {
        Customer c = new Customer();

        c.Name = "John (" + g + ")" ;

        for (int i = 0; i < 3; i++)
        {
            c.Bills.Add(CreateMockBill());
        }

        return c;
    }

    private Bill CreateMockBill()
    {
        Bill b = new Bill();

        b.Price = 55.5;
        b.BoughtOnDate = DateTime.Now.Date;

        return b;
    }

    public event PropertyChangedEventHandler PropertyChanged = delegate { };
}


public class Customer : INotifyPropertyChanged
{
    private string name;
    public string Name
    {
        get { return name; }
        set { name = value; }
    }

    private ObservableCollection<Bill> bills;
    public ObservableCollection<Bill> Bills
    {
        get
        {
            if (bills == null)
            {
                bills = new ObservableCollection<Bill>();
            }
            return bills;
        }
    }


    public event PropertyChangedEventHandler PropertyChanged = delegate { };
}

public class Bill : INotifyPropertyChanged
{
    private double price;
    public double Price
    {
        get { return price; }
        set
        {
            price = value;
            PropertyChanged(this, new PropertyChangedEventArgs("Price"));
        }
    }

    private DateTime boughtOnDate;
    public DateTime BoughtOnDate
    {
        get { return boughtOnDate; }
        set
        {
            boughtOnDate = value;
            PropertyChanged(this, new PropertyChangedEventArgs("BoughtOnDate"));
        }
    }


    public event PropertyChangedEventHandler PropertyChanged = delegate { };
}

public interface IRelayCommand : ICommand
{
    void RaiseCanExecuteChanged();
}
public class RelayCommand<T> : IRelayCommand
{
    private Predicate<T> _canExecute;
    private Action<T> _execute;

    public RelayCommand(Action<T> execute, Predicate<T> canExecute = null)
    {
        _execute = execute;
        _canExecute = canExecute;
    }

    private void Execute(T parameter)
    {
        _execute(parameter);
    }

    private bool CanExecute(T parameter)
    {
        return _canExecute == null ? true : _canExecute(parameter);
    }

    public bool CanExecute(object parameter)
    {
        return parameter == null ? false : CanExecute((T)parameter);
    }

    public void Execute(object parameter)
    {
        _execute((T)parameter);
    }

    public event EventHandler CanExecuteChanged;

    public void RaiseCanExecuteChanged()
    {
        var temp = Volatile.Read(ref CanExecuteChanged);

        if (temp != null)
            temp(this, new EventArgs());
    }
}

XAML:
     <Window>
         <Window.Resources>
           <HierarchicalDataTemplate x:Key="customerTemplate" DataType="{x:Type local:Customer}" ItemsSource="{Binding Bills}">
               <HierarchicalDataTemplate.ItemTemplate>
                  <DataTemplate>
                    <Grid>
                       <Grid.ColumnDefinitions>
                           <ColumnDefinition/>
                           <ColumnDefinition/>
                           <ColumnDefinition/>
                       </Grid.ColumnDefinitions>

                       <TextBlock Text="{Binding Price}" />
                       <TextBlock Text="{Binding BoughtOnDate}" Grid.Column="1" />
                       <Button Content="Edit" Grid.Column="2"
                            Command="{Binding RelativeSource={RelativeSource AncestorType=Window},Path=DataContext.OnEditBillCommand}" 
                            CommandParameter="{Binding}"/> 

                   </Grid>
               </DataTemplate>
           </HierarchicalDataTemplate.ItemTemplate>

           <TextBlock Text="{Binding Name}" FontFamily="Arial" FontSize="16" FontWeight="Bold" />

        </HierarchicalDataTemplate>

   </Window.Resources>

   <Grid>
       <Grid.ColumnDefinitions>
           <ColumnDefinition />
           <ColumnDefinition Width="0.05*"/>
           <ColumnDefinition />
       </Grid.ColumnDefinitions>


       <TreeView ItemsSource="{Binding Customers}" ItemTemplate="{StaticResource customerTemplate}">

       </TreeView>


       <Grid Grid.Column="2" DataContext="{Binding CurrentBill, Mode=OneWay}" Background="AliceBlue">
           <Grid.RowDefinitions>
              <RowDefinition />
              <RowDefinition />
           </Grid.RowDefinitions>

           <TextBox Text="{Binding Price, Mode=TwoWay}"  Margin="50"/>
           <TextBox Text="{Binding BoughtOnDate, Mode=TwoWay}" Grid.Row="1" Margin="50"/>


      </Grid>

   </Grid>     

这很接近我想要的,除了我希望当显示细节时,树形视图可以“消失”(然后在关闭细节时重新出现)。让我尝试一下这个和上面的其他想法。 - Eric Logsdon
是的,我知道,我以为你可以从这里提取出来。 没问题,我不能编辑这个来做到那样。 - eran otzap

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