2012-06-17 13 views
8

Tôi có ứng dụng MVVM với một TreeView WPF ở phía bên trái của một cửa sổ. Bảng chi tiết ở bên phải thay đổi nội dung tùy thuộc vào nút cây được chọn.Làm thế nào để tìm ra nếu được chọn bằng chuột hoặc phím?

Nếu người dùng chọn một nút, nội dung của bảng chi tiết sẽ thay đổi ngay lập tức. Đó là mong muốn nếu người dùng nhấp vào nút, nhưng tôi muốn trì hoãn thay đổi nội dung nếu người dùng điều hướng cây bằng cách sử dụng phím xuống/lên. (Hành vi tương tự như Windows Explorer, ít nhất theo Win XP) Tôi cho rằng tôi phải biết trong ViewModel của mình nếu nút đã được chọn qua chuột hoặc bàn phím.

Tôi làm cách nào để đạt được điều này?

Cập nhật:

Đây là bài viết đầu tiên của tôi vì vậy tôi không chắc chắn nếu điều này là đúng nơi, nhưng tôi muốn để cho cộng đồng biết những gì tôi đã làm trong khi chờ đợi. Đây là giải pháp của riêng tôi. Tôi không phải là một chuyên gia do đó tôi không biết nếu đó là một giải pháp tốt. Nhưng nó làm việc cho tôi và tôi sẽ hạnh phúc nếu nó giúp người khác. Sửa lỗi, cải tiến hoặc giải pháp tốt hơn được đánh giá cao.

Tôi tạo ra dưới đây sở hữu gắn HasMouseFocus ...
(Trước tiên tôi sử dụng MouseEnterEvent nhưng điều này không làm việc tốt nếu dùng điều hướng cây với phím lên/xuống và con trỏ chuột là ngẫu nhiên đối với bất kỳ cây lèo lái mục, bởi vì trong trường hợp đó các chi tiết được cập nhật ngay lập tức.)

public static bool GetHasMouseFocus(TreeViewItem treeViewItem) 
{ 
    return (bool)treeViewItem.GetValue(HasMouseFocusProperty); 
} 

public static void SetHasMouseFocus(TreeViewItem treeViewItem, bool value) 
{ 
    treeViewItem.SetValue(HasMouseFocusProperty, value); 
} 

public static readonly DependencyProperty HasMouseFocusProperty = 
    DependencyProperty.RegisterAttached(
    "HasMouseFocus", 
    typeof(bool), 
    typeof(TreeViewItemProperties), 
    new UIPropertyMetadata(false, OnHasMouseFocusChanged) 
); 

static void OnHasMouseFocusChanged(
    DependencyObject depObj, DependencyPropertyChangedEventArgs e) 
{ 
    TreeViewItem item = depObj as TreeViewItem; 
    if (item == null) 
    return; 

    if (e.NewValue is bool == false) 
    return; 

    if ((bool)e.NewValue) 
    { 
    item.MouseDown += OnMouseDown; 
    item.MouseLeave += OnMouseLeave; 
    } 
    else 
    { 
    item.MouseDown -= OnMouseDown; 
    item.MouseLeave -= OnMouseLeave; 
    } 
} 

/// <summary> 
/// Set HasMouseFocusProperty on model of associated element. 
/// </summary> 
/// <param name="sender"></param> 
/// <param name="e"></param> 
static void OnMouseDown(object sender, MouseEventArgs e) 
{ 
    if (sender != e.OriginalSource) 
    return; 

    TreeViewItem item = sender as TreeViewItem; 
    if ((item != null) & (item.HasHeader)) 
    { 
    // get the underlying model of current tree item 
    TreeItemViewModel header = item.Header as TreeItemViewModel; 
    if (header != null) 
    { 
     header.HasMouseFocus = true; 
    } 
    } 
} 

/// <summary> 
/// Clear HasMouseFocusProperty on model of associated element. 
/// </summary> 
/// <param name="sender"></param> 
/// <param name="e"></param> 
static void OnMouseLeave(object sender, MouseEventArgs e) 
{ 
    if (sender != e.OriginalSource) 
    return; 

    TreeViewItem item = sender as TreeViewItem; 
    if ((item != null) & (item.HasHeader)) 
    { 
    // get the underlying model of current tree item 
    TreeItemViewModel header = item.Header as TreeItemViewModel; 
    if (header != null) 
    { 
     header.HasMouseFocus = false; 
    } 
    } 
} 

... và áp dụng nó vào TreeView.ItemContainerStyle

<TreeView.ItemContainerStyle> 
     <Style TargetType="{x:Type TreeViewItem}" > 
     <!-- These Setters binds some properties of a TreeViewItem to the TreeViewItemViewModel. --> 
     <Setter Property="IsExpanded" Value="{Binding Path=IsExpanded, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> 
     <Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> 
     <Setter Property="ToolTip" Value="{Binding Path=CognosBaseClass.ToolTip}"/> 
     <!-- These Setters applies attached behaviors to all TreeViewItems. --> 
     <Setter Property="properties:TreeViewItemProperties.PreviewMouseRightButtonDown" Value="True" /> 
     <Setter Property="properties:TreeViewItemProperties.BringIntoViewWhenSelected" Value="True" /> 
     <Setter Property="properties:TreeViewItemProperties.HasMouseFocus" Value="True" /> 
     </Style> 
    </TreeView.ItemContainerStyle> 

Trong đó thuộc tính là đường dẫn thuộc tính đính kèm của tôi.

xmlns:properties="clr-namespace:WPF.MVVM.AttachedProperties;assembly=WPF.MVVM" 

Sau đó, trong ViewModel của tôi, nếu HasMousefocusProperty là sự thật, tôi cập nhật các bảng chi tiết (GridView) ngay lập tức. Nếu sai tôi chỉ đơn giản là bắt đầu một DispatcherTimer và áp dụng các mục hiện đang được chọn làm Tag. Sau khoảng thời gian 500ms, sự kiện Tick sẽ áp dụng các chi tiết, nhưng chỉ khi mục đã chọn vẫn giống với Thẻ.

/// <summary> 
/// This property is beeing set when the selected item of the tree has changed. 
/// </summary> 
public TreeItemViewModel SelectedTreeItem 
{ 
    get { return Property(() => SelectedTreeItem); } 
    set 
    { 
    Property(() => SelectedTreeItem, value); 
    if (this.SelectedTreeItem.HasMouseFocus) 
    { 
     // show details for selected node immediately 
     ShowGridItems(value); 
    } 
    else 
    { 
     // delay showing details 
     this._selctedNodeChangedTimer.Stop(); 
     this._selctedNodeChangedTimer.Tag = value; 
     this._selctedNodeChangedTimer.Start(); 
    } 
    } 
} 
+0

Hai câu trả lời hàng đầu trong thỏa thuận SO câu hỏi sau với vấn đề liên quan đến khóa: http://stackoverflow.com/a/7086853/1416258 và http://stackoverflow.com/a/918062/1416258 Theo như của bạn tình hình cá nhân, hoặc chơi xung quanh hoặc chờ ai đó cho bạn một con cá. –

+0

Rất cám ơn @Geoist. Sử dụng một ràng buộc cammand tôi có thể thiết lập một lá cờ trong ViewModel của tôi ngay sau khi người dùng nhấn (các) phím được chỉ định. Nhưng tôi phải xóa cờ này một lần nữa khi (hoặc trước đó) sau đó người dùng chọn một nút cây khác. Bất kỳ ý tưởng? – AelanY

+0

@AelanY Vâng, bạn nói đúng, bạn sẽ phải chăm sóc đặt lại cờ này, nơi tốt nhất sẽ là logic nơi bạn đang bắt đầu làm mới bảng chi tiết của bạn. – akjoshi

Trả lời

2

Bạn có thể xử lý các OnPreviewKeyDown cho bạn TreeView (hoặc điều khiển người dùng có nó) và lập trình thiết lập một lá cờ trong ViewModel của bạn và xem xét nó trong khi các chi tiết làm mới bảng điều khiển -

protected override void OnPreviewKeyDown(System.Windows.Input.KeyEventArgs e) 
{ 
    switch(e.Key) 
    { 
     case Key.Up: 
     case Key.Down: 
      MyViewModel.IsUserNavigating = true; 
      break; 
    } 
} 

Một approch tương tự và các giải pháp khác được đề cập trong câu hỏi SO này -

How can I programmatically navigate (not select, but navigate) a WPF TreeView?

Cập nhật: [Trả lời nhận xét của AalanY]

Tôi không nghĩ có bất kỳ vấn đề nào trong việc có một số mã trong Chế độ xem, điều đó không phá vỡ MVVM.

Trong bài viết, WPF Apps With The Model-View-ViewModel Design Pattern, tác giả là người Josh Smith nói:

Trong một kiến ​​trúc MVVM được thiết kế tốt, codebehind cho hầu hết các lần xem nên để trống, hoặc, ít nhất, chỉ chứa mã điều khiển các điều khiển và tài nguyên của chứa trong chế độ xem đó. Đôi khi nó là cũng cần thiết để viết mã trong một Xem codebehind mà tương tác với một đối tượng ViewModel, chẳng hạn như lôi một sự kiện hoặc gọi một phương thức mà nếu không sẽ rất khó khăn để gọi từ ViewModel riêng của mình.

Theo kinh nghiệm của tôi nó không thể xây dựng một doanh nghiệp (kích thước đáng kể) ứng dụng mà không có bất kỳ mã phía sau, đặc biệt khi bạn phải sử dụng điều khiển phức tạp như TreeView, DataGrid hoặc điều khiển bên 3'rd.

+0

Cảm ơn câu trả lời của bạn @akjoshi, nhưng tôi đang sử dụng mẫu MVVM và tôi không muốn có bất kỳ mã nào phía sau. – AelanY

+0

Cảm ơn một lần nữa @akjoshi, tôi đã đọc bài viết tuyệt vời này của Josh Smith và tất nhiên bạn là đúng. Bạn nghĩ gì về giải pháp của tôi? Vui lòng xem nội dung cập nhật của tôi ở trên ... – AelanY

+0

@AelanY Giải pháp của bạn có vẻ tốt vì nó có thể được đính kèm với một lượt xem tre trong XAML và có các yếu tố có thể sử dụng lại; ngoại trừ điều này, nó gần giống như sử dụng trình xử lý sự kiện và cập nhật cờ ViewModel. – akjoshi