2009-06-18 30 views
59

Có ai khác nhận thấy rằng Bindings with ElementName không giải quyết chính xác cho các đối tượng MenuItem được chứa trong các đối tượng ContextMenu không? Hãy xem mẫu này:ElementName Binding từ MenuItem trong ContextMenu

<Window x:Class="EmptyWPF.Window1" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Title="Window1" Height="300" Width="300" 
    x:Name="window"> 
    <Grid x:Name="grid" Background="Wheat"> 
     <Grid.ContextMenu> 
      <ContextMenu x:Name="menu"> 
       <MenuItem x:Name="menuItem" Header="Window" Tag="{Binding ElementName=window}" Click="MenuItem_Click"/> 
       <MenuItem Header="Grid" Tag="{Binding ElementName=grid}" Click="MenuItem_Click"/> 
       <MenuItem Header="Menu" Tag="{Binding ElementName=menu}" Click="MenuItem_Click"/> 
       <MenuItem Header="Menu Item" Tag="{Binding ElementName=menuItem}" Click="MenuItem_Click"/> 
      </ContextMenu> 
     </Grid.ContextMenu> 
     <Button Content="Menu" 
       HorizontalAlignment="Center" VerticalAlignment="Center" 
       Click="MenuItem_Click" Tag="{Binding ElementName=menu}"/> 
     <Menu HorizontalAlignment="Center" VerticalAlignment="Bottom"> 
      <MenuItem x:Name="anotherMenuItem" Header="Window" Tag="{Binding ElementName=window}" Click="MenuItem_Click"/> 
      <MenuItem Header="Grid" Tag="{Binding ElementName=grid}" Click="MenuItem_Click"/> 
      <MenuItem Header="Menu" Tag="{Binding ElementName=menu}" Click="MenuItem_Click"/> 
      <MenuItem Header="Menu Item" Tag="{Binding ElementName=anotherMenuItem}" Click="MenuItem_Click"/> 
     </Menu> 
    </Grid> 
</Window> 

Tất cả các liên kết đều hoạt động tốt ngoại trừ các ràng buộc có trong ContextMenu. Họ in một lỗi đến cửa sổ Output trong suốt thời gian chạy.

Bất kỳ ai biết về bất kỳ công việc nào xung quanh? Những gì đang xảy ra ở đây?

+0

Vấn đề rõ ràng là phải làm gì đó với các kính ngắm ... –

+0

Do ContextMenus có định nghĩa tên riêng của chúng theo mặc định không? –

Trả lời

51

Tôi đã tìm thấy giải pháp đơn giản hơn nhiều.

Trong đoạn code sau cho UserControl:

NameScope.SetNameScope(contextMenu, NameScope.GetNameScope(this)); 
+0

Điều này dường như không hoạt động nữa trong khung 4.0. –

+0

Xin lỗi, tôi chưa thử trên 4.0 –

+5

Thực ra, nó hoạt động với tôi trong 4.0. – esylvestre

4

Sau khi thử nghiệm một chút, tôi phát hiện ra một công trình xung quanh:

Make cấp cao nhất Window/UserControl thực hiện INameScope và thiết lập NameScope của ContextMenu để điều khiển cấp cao nhất.

public class Window1 : Window, INameScope 
{ 
    public Window1() 
    { 
     InitializeComponent(); 
     NameScope.SetNameScope(contextMenu, this); 
    } 

    // Event handlers and etc... 

    // Implement INameScope similar to this: 
    #region INameScope Members 

    Dictionary<string, object> items = new Dictionary<string, object>(); 

    object INameScope.FindName(string name) 
    { 
     return items[name]; 
    } 

    void INameScope.RegisterName(string name, object scopedElement) 
    { 
     items.Add(name, scopedElement); 
    } 

    void INameScope.UnregisterName(string name) 
    { 
     items.Remove(name); 
    } 

    #endregion 
} 

Điều này cho phép menu ngữ cảnh tìm các mục được đặt tên trong số Window. Bất kỳ tùy chọn nào khác?

5

Trình đơn ngữ cảnh rất khó để ràng buộc. Chúng tồn tại bên ngoài cây trực quan của điều khiển của bạn, do đó chúng không thể tìm thấy tên phần tử của bạn.

Thử đặt datacontext của menu ngữ cảnh của bạn thành mục tiêu vị trí. Bạn phải sử dụng RelativeSource.

<ContextMenu 
    DataContext="{Binding PlacementTarget, RelativeSource={RelativeSource Self}}"> ... 
+0

Đặt DataContext thành Mục tiêu vị trí sẽ ảnh hưởng đến các ràng buộc của ElementName? Tôi nghĩ DataContext chỉ được sử dụng cho Bindings không có thuộc tính Source, RelativeSource hoặc ElementName. –

+0

Đặt thuộc tính ElementName sẽ chỉ hoạt động nếu trình quản lý bố cục có thể tìm thấy phần tử được liên kết bằng cách điều hướng cây trực quan. Menu ngữ cảnh không tồn tại bên trong cây trực quan của điều khiển mà chúng được thêm vào. Bạn phải đặt datacontext của trình đơn ngữ cảnh để trình quản lý bố cục có thể điều hướng lên cây thị giác của mục tiêu vị trí của nó để tìm phần tử được liên kết. – Josh

+0

Thêm DataContext vào ví dụ trên không khắc phục được sự cố. Tôi vẫn có lỗi sau trong cửa sổ Output: "System.Windows.Data Error: 4: Không thể tìm thấy nguồn để liên kết với tham chiếu 'ElementName = window'. BindingExpression: (không có đường dẫn); DataItem = null; phần tử đích là 'MenuItem '(Name =' menuItem '); thuộc tính mục tiêu là' Thẻ '(loại' Đối tượng ') " –

19

Đây là một workaround XAML-only. (Điều này cũng giả định bạn muốn gì bên trong các DataContext, ví dụ, bạn MVVMing nó)

Lựa chọn một, nơi mà các yếu tố phụ huynh của ContextMenu không phải là trong một DataTemplate:

Command="{Binding PlacementTarget.DataContext.MyCommand, 
     RelativeSource={RelativeSource AncestorType=ContextMenu}}" 

Điều này sẽ phù hợp với câu hỏi của OP. Điều này sẽ không hoạt động nếu bạn ở trong một số DataTemplate. Trong những trường hợp này, DataContext thường là một trong nhiều bộ sưu tập và ICommand bạn muốn liên kết là thuộc tính anh chị em của bộ sưu tập trong cùng một ViewModel (số DataContext của Cửa sổ).

Trong những trường hợp này, bạn có thể tận dụng lợi thế của Tag tạm giữ phụ huynh DataContext trong đó có cả bộ sưu tập VÀ ICommand của bạn:

class ViewModel 
{ 
    public ObservableCollection<Derp> Derps { get;set;} 
    public ICommand DeleteDerp {get; set;} 
} 

và trong XAML

<!-- ItemsSource binds to Derps in the DataContext --> 
<StackPanel 
    Tag="{Binding DataContext, ElementName=root}"> 
    <StackPanel.ContextMenu> 
     <ContextMenu> 
      <MenuItem 
       Header="Derp"      
       Command="{Binding PlacementTarget.Tag.DeleteDerp, 
       RelativeSource={RelativeSource 
            AncestorType=ContextMenu}}" 
       CommandParameter="{Binding PlacementTarget.DataContext, 
       RelativeSource={RelativeSource AncestorType=ContextMenu}}"> 
      </MenuItem> 
+0

Tôi nghĩ rằng điểm liên quan mà bạn đang thực hiện ở đây là bạn có thể sử dụng thẻ và các ràng buộc nguồn tương đối để lấy dữ liệu ở một vị trí khác trong cây trực quan. –

+0

Điều này không thực sự liên quan đến MVVM. Tôi chỉ sử dụng các liên kết ElementName khi tôi đang cố gắn hai điều khiển liên quan với nhau bên ngoài máy ảo. Đây là một giải pháp tốt để ràng buộc các mục menu ngữ cảnh với các lệnh trên máy ảo. Một lựa chọn tốt là sử dụng lệnh được định tuyến có liên quan đến VM. Một ví dụ điển hình là lớp CommandSink của Josh Smith. –

1

Tôi không chắc chắn lý do tại sao nên sử dụng các thủ thuật ma thuật chỉ để tránh một dòng mã bên trong tổ chức sự kiện cho nhấp chuột mà bạn đã xử lý:

private void MenuItem_Click(object sender, System.Windows.RoutedEventArgs e) 
    { 
     // this would be your tag - whatever control can be put as string intot he tag 
     UIElement elm = Window.GetWindow(sender as MenuItem).FindName("whatever control") as UIElement; 
    } 
+0

Làm theo cách này không cho phép mục menu tự động bị vô hiệu hóa theo lệnh bị ràng buộc. Vì vậy, trong khi nó hoạt động để thực hiện, bạn sẽ phải thêm nhiều mã hơn để cũng vô hiệu hóa/kích hoạt mục menu cho phù hợp khi nó được nạp. Không phải là điều này là xấu, nó chỉ mang lại những kỷ niệm xấu của spaghetti mã WinFoms UI cho rất nhiều người. – jpierson

16

Như được người khác nói, 'ContextMenu' không được chứa trong cây thị giác và liên kết 'ElementName' sẽ không hoạt động. Đặt 'NameScope' của menu ngữ cảnh như được đề xuất bởi câu trả lời được chấp nhận chỉ hoạt động nếu menu ngữ cảnh không được định nghĩa trong 'DataTemplate'. Tôi đã giải quyết điều này bằng cách sử dụng các {x:Reference} Markup-Extension đó là tương tự như 'ElementName' ràng buộc nhưng giải quyết các ràng buộc khác nhau, bỏ qua các cây thị giác. Tôi cho rằng điều này dễ đọc hơn nhiều so với sử dụng 'PlacementTarget'. Dưới đây là một ví dụ:

<Image Source="{Binding Image}">  
    <Image.ContextMenu> 
     <ContextMenu> 
      <MenuItem Header="Delete" 
         Command="{Binding Source={x:Reference Name=Root}, Path=DataContext.RemoveImage}" 
         CommandParameter="{Binding}" /> 
     </ContextMenu> 
    </Image.ContextMenu> 
</Image> 

Theo MSDN-tài liệu

x:Reference is a construct defined in XAML 2009. In WPF, you can use XAML 2009 features, but only for XAML that is not WPF markup-compiled. Markup-compiled XAML and the BAML form of XAML do not currently support the XAML 2009 language keywords and features.

bất cứ điều gì đó có nghĩa là ... Làm việc cho tôi, mặc dù.

+4

Trên thực tế lời nhận xét hữu ích nhất của nó ở đây –

+1

Điều này thực sự hiệu quả nhất, tôi đã thử tất cả các câu trả lời và điều này là đúng cho WPF .Net 4.5.2. Cảm ơn bạn @Marc –

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