2009-07-01 24 views
15

Khi tôi ràng buộc Menu Items với một ObservableCollection, chỉ có "bên trong" khu vực của MenuItem là nhấp được:Làm cách nào để gắn kết một ObservableCollection của ViewModels với một MenuItem?

alt text http://tanguay.info/web/external/mvvmMenuItems.png

Trong tôi Xem Tôi có menu này:

<Menu> 
    <MenuItem 
     Header="Options" ItemsSource="{Binding ManageMenuPageItemViewModels}" 
       ItemTemplate="{StaticResource MainMenuTemplate}"/> 
</Menu> 

Sau đó, Tôi liên kết nó với số này DataTemplate:

<DataTemplate x:Key="MainMenuTemplate"> 
    <MenuItem 
     Header="{Binding Title}" 
     Command="{Binding DataContext.SwitchPageCommand, 
     RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Menu}}}" 
     Background="Red" 
     CommandParameter="{Binding IdCode}"/> 
</DataTemplate> 

Vì mỗi ViewModel trong ObservableCollection ManageMenuPageItemViewModels có một tài sản Tiêu đềIdCode, các mã trên hoạt động tốt ngay từ cái nhìn đầu tiên.

TUY NHIÊN, vấn đề là các MenuItem trong DataTemplate thực là bên trong khác MenuItem (như thể nó đang được ràng buộc hai lần) để trong DataTemplate ở trên với Background =" Màu đỏ " có một hộp màu Màu đỏ bên trong mỗi mục menu và chỉ có thể nhấp vào khu vực này, không phải toàn bộ khu vực mục menu (ví dụ: nếu người dùng nhấp vào khu vực có dấu kiểm hoặc sang phải hoặc trái của cl bên trong khu vực có thể xảy ra, sau đó không có gì xảy ra, trong đó, nếu bạn không có màu sắc riêng biệt thì rất khó hiểu.)

Cách chính xác để ràng buộc MenuItems là một ObservableCollection của ViewModels sao cho toàn bộ khu vực bên trong mỗi MenuItem là có thể nhấp?

UPDATE:

Vì vậy, tôi đã thực hiện những thay đổi sau đây dựa trên những lời khuyên dưới đây và bây giờ có điều này:

alt text http://tanguay.info/web/external/mvvmMenuItemsYellow.png

Tôi chỉ có một TextBlock bên trong DataTemplate của tôi, nhưng tôi vẫn không thể "tô màu toàn bộ MenuItem" nhưng chỉ có TextBlock:

<DataTemplate x:Key="MainMenuTemplate"> 
    <TextBlock Text="{Binding Title}"/> 
</DataTemplate> 

Và tôi đặt lệnh ràng buộc vào Menu.ItemCo ntainerStyle nhưng họ không bắn bây giờ:

<Menu DockPanel.Dock="Top"> 
    <Menu.ItemContainerStyle> 
     <Style TargetType="MenuItem"> 
      <Setter Property="Background" Value="Yellow"/> 
      <Setter Property="Command" Value="{Binding DataContext.SwitchPageCommand, 
     RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Menu}}}"/> 
      <Setter Property="CommandParameter" Value="{Binding IdCode}"/> 
     </Style> 
    </Menu.ItemContainerStyle> 
    <MenuItem 
     Header="MVVM" ItemsSource="{Binding MvvmMenuPageItemViewModels}" 
       ItemTemplate="{StaticResource MainMenuTemplate}"/> 
    <MenuItem 
     Header="Application" ItemsSource="{Binding ApplicationMenuPageItemViewModels}" 
       ItemTemplate="{StaticResource MainMenuTemplate}"/> 
    <MenuItem 
     Header="Manage" ItemsSource="{Binding ManageMenuPageItemViewModels}" 
       ItemTemplate="{StaticResource MainMenuTemplate}"/> 
</Menu> 

Trả lời

35

Tôi thấy sử dụng MVVM với MenuItems rất khó khăn. Phần còn lại của ứng dụng của tôi sử dụng DataTemplates để ghép nối View với ViewModel, nhưng điều đó dường như không làm việc với Menus vì chính xác lý do bạn đã mô tả. Đây là cách tôi cuối cùng đã giải quyết nó. Chế độ xem của tôi trông giống như sau:

<DockPanel> 
<Menu DockPanel.Dock="Top" ItemsSource="{Binding Path=(local:MainViewModel.MainMenu)}"> 
    <Menu.ItemContainerStyle> 
     <Style> 
      <Setter Property="MenuItem.Header" Value="{Binding Path=(contracts:IMenuItem.Header)}"/> 
      <Setter Property="MenuItem.ItemsSource" Value="{Binding Path=(contracts:IMenuItem.Items)}"/> 
      <Setter Property="MenuItem.Icon" Value="{Binding Path=(contracts:IMenuItem.Icon)}"/> 
      <Setter Property="MenuItem.IsCheckable" Value="{Binding Path=(contracts:IMenuItem.IsCheckable)}"/> 
      <Setter Property="MenuItem.IsChecked" Value="{Binding Path=(contracts:IMenuItem.IsChecked)}"/> 
      <Setter Property="MenuItem.Command" Value="{Binding}"/> 
      <Setter Property="MenuItem.Visibility" Value="{Binding Path=(contracts:IMenuItem.Visible), 
       Converter={StaticResource BooleanToVisibilityConverter}}"/> 
      <Setter Property="MenuItem.ToolTip" Value="{Binding Path=(contracts:IMenuItem.ToolTip)}"/> 
      <Style.Triggers> 
       <DataTrigger Binding="{Binding Path=(contracts:IMenuItem.IsSeparator)}" Value="true"> 
        <Setter Property="MenuItem.Template"> 
         <Setter.Value> 
          <ControlTemplate TargetType="{x:Type MenuItem}"> 
           <Separator Style="{DynamicResource {x:Static MenuItem.SeparatorStyleKey}}"/> 
          </ControlTemplate> 
         </Setter.Value> 
        </Setter> 
       </DataTrigger> 
      </Style.Triggers> 
     </Style> 
    </Menu.ItemContainerStyle> 
</Menu> 
</DockPanel> 

Nếu bạn nhận thấy, tôi đã xác định giao diện được gọi là IMenuItem, là ViewModel cho MenuItem. Đây là mã cho điều đó:

public interface IMenuItem : ICommand 
{ 
    string Header { get; } 
    IEnumerable<IMenuItem> Items { get; } 
    object Icon { get; } 
    bool IsCheckable { get; } 
    bool IsChecked { get; set; } 
    bool Visible { get; } 
    bool IsSeparator { get; } 
    string ToolTip { get; } 
} 

Lưu ý rằng IMenuItem định nghĩa các mục có thể đếm được, đó là cách bạn nhận các menu phụ. Ngoài ra, IsSeparator là một cách để xác định dấu phân cách trong menu (một mẹo nhỏ khó khăn khác). Bạn có thể thấy trong xaml cách nó sử dụng một DataTrigger để thay đổi kiểu dáng thành kiểu phân cách hiện tại nếu IsSeparator là đúng. Dưới đây là cách MainViewModel định nghĩa thuộc tính MainMenu (mà chế độ xem liên kết với):

public IEnumerable<IMenuItem> MainMenu { get; set; } 

Điều này có vẻ hoạt động tốt. Tôi giả sử bạn có thể sử dụng ObservableCollection cho MainMenu. Tôi đang thực sự sử dụng MEF để soạn trình đơn ra khỏi các phần, nhưng sau đó các mục của chúng là tĩnh (mặc dù các thuộc tính của từng mục trình đơn không). Tôi cũng sử dụng một lớp AbstractMenuItem thực hiện IMenuItem và là một lớp trợ giúp để tạo các mục menu trong các phần khác nhau.

UPDATE:

Về vấn đề màu sắc của bạn, không this thread giúp đỡ?

+1

+1 - rất hay cho ví dụ đầy đủ với Dấu tách và mọi thứ. –

+0

Tôi có thiết kế rất giống nhau và mọi thứ hoạt động ngoại trừ việc hiển thị dấu phân cách. Nếu tôi thay đổi mẫu thành thì tôi sẽ thấy "Dấu phân cách" trong đó dấu phân cách phải. Nhưng khi tôi cố gắng sử dụng các mẫu như trong câu trả lời của bạn không có gì được hiển thị. Tôi đã thử và sau đó dấu phân cách hiển thị nhưng tôi muốn kiểu mặc định với chiều rộng được thiết lập động theo chiều rộng trình đơn. Tôi có nên định nghĩa SeparatorStyleKey ở đâu đó không? Tôi đã tìm kiếm trực tuyến nhưng không thể tìm thấy bất kỳ thứ gì đã giúp ...Cảm ơn! – Dina

14

Đừng đặt MenuItem trong DataTemplate. Số DataTemplate xác định nội dung của số MenuItem. Thay vào đó, xác định tính chất không liên quan cho MenuItem qua ItemContainerStyle:

<Menu> 
    <Menu.ItemContainerStyle> 
     <Style TargetType="MenuItem"> 
      <Setter Property="Header" Value="{Binding Title}"/> 
      ... 
     </Style> 
    </Menu.ItemContainerStyle> 
    <MenuItem 
     Header="Options" ItemsSource="{Binding ManageMenuPageItemViewModels}" 
       ItemTemplate="{StaticResource MainMenuTemplate}"/> 
</Menu> 

Ngoài ra, hãy nhìn vào HierarchicalDataTemplate s.

+0

bạn có định nghĩa tiêu đề/màu trong Menu.ItemContainerStyle và sau đó bên trong DataTemplate đặt một HierarchicalDataTemplate định nghĩa Command và CommandParameter? –

+0

Cảm ơn, đây chính xác là tôi đang tìm kiếm. Hoạt động tuyệt vời. Cảm ơn! –

+0

+1 - HierarchicalDataTemplates làm cho toàn bộ vấn đề này gần như tầm thường. –

2

Đây là cách tôi đã thực hiện các menu của mình. Nó có thể không chính xác những gì bạn cần, nhưng tôi nghĩ nó khá gần.

<Style x:Key="SubmenuItemStyle" TargetType="MenuItem"> 
    <Setter Property="Header" Value="{Binding MenuName}"></Setter> 
    <Setter Property="Command" Value="{Binding Path=MenuCommand}"/> 
    <Setter Property="ItemsSource" Value="{Binding SubmenuItems}"></Setter> 
    </Style> 

    <DataTemplate DataType="{x:Type systemVM:TopMenuViewModel}" > 
    <Menu> 
     <MenuItem Header="{Binding MenuName}"   
        ItemsSource="{Binding SubmenuItems}" 
        ItemContainerStyle="{DynamicResource SubmenuItemStyle}" /> 
    </Menu> 
    </DataTemplate> 

    <Menu DockPanel.Dock="Top" ItemsSource="{Binding Menus}" /> 

TopMenuViewModel là tập hợp các menu sẽ xuất hiện trên thanh trình đơn. Chúng đều chứa MenuName sẽ được hiển thị và một bộ sưu tập có tên SubMenuItems mà tôi đặt là ItemsSource.

Tôi kiểm soát cách SubMenuItems được hiển thị theo kiểu SumMenuItemStyle. Mỗi SubMenuItem có thuộc tính MenuName của riêng nó, thuộc tính Command của kiểu ICommand, và có thể là một tập hợp các SubMenuItems khác.

Kết quả là tôi có thể lưu trữ tất cả thông tin trình đơn của mình trong cơ sở dữ liệu và tự động chuyển đổi menu nào được hiển thị khi chạy. Toàn bộ khu vực menuitem có thể nhấp và hiển thị chính xác.

Hy vọng điều này sẽ hữu ích.

2

Chỉ cần tạo DataTemplate của bạn thành một TextBlock (hoặc có thể là ngăn xếp với biểu tượng và một TextBlock).

+0

ok đó là tuyệt vời, nó hoạt động (tôi nghĩ rằng tôi đã cố gắng đó), nhưng bây giờ tôi phải treo lệnh bằng cách nào đó để TextBlock, nó không có một thuộc tính Command, tôi không thể sử dụng DelegateCommand của tôi, bạn đã sử dụng AttachedBehaviors hoặc cái gì khác? –

+1

Chỉnh sửa ItemContainerStyle –

+0

chỉnh sửa nó cho phù hợp ở trên, đăng ảnh chụp màn hình, vẫn không hoạt động :-( –

Các vấn đề liên quan