簡體   English   中英

如何在沒有 INotifyPropertyChanged 的​​情況下將 XAML 命令綁定到查看或更新 DataGrid 項目

[英]How to bind XAML Command to View -or- Update DataGrid item(s) on modification without INotifyPropertyChanged

我在一個窗口中有一個 WPF DataGrid,它帶有關聯的 View(*.xaml.cs) 和 ViewModel,它成功地執行了一系列功能。 但是,修改項目而不是更改集合的函數在排序、調整大小等之前不會更新。

我發現一堆搜索結果表明解決方案是使項目類型實現 INotifyPropertyChanged 並根據集合中的每個項目添加/減去事件處理程序。 我嘗試使用這些示例並沒有成功,但坦率地說,這似乎不是一個很好的選擇。

項目類型在應用程序的其他地方聲明,並與其他模塊共享對象實例,因此我不想修改該類。 將 DataGrid 的 ItemsSource 的實現與其中的項目類型聯系起來似乎也是一個糟糕的設計; 列表容器已經是一個根據需要調用 OnPropertyChanged 的​​ ObservableCollection,那么為什么這還不夠呢?

我可以通過 DataGrid.Items.Refresh() 進行更新 - 不幸的是,它似乎沒有對特定項目/屬性進行重載,而不是更新整個列表,但這是一個小問題 - 但只有我的 View 引用了DataGrid 本身(每個 MVVM),而 Command 綁定在 ViewModel 中。

我實際上想將這些命令綁定放在視圖中,我不明白為什么約定將它們放在 VM 中,從而在 UI 事件期間繞過視圖。 例如,要刪除項目,我可以選擇它們,然后按 Delete(KeyUp 處理程序在視圖中,然后將所選項目作為列表傳遞給 VM)或在上下文菜單中選擇刪除(綁定到 VM 中的 ICommand ,它路由到視圖調用的相同函數)。 為什么將兩者都綁定到視圖的事件處理程序(或視圖中的兩個處理程序)不是更可取的?

我已經看到一些結果使用RelativeSource 綁定到類型UserControl 的祖先的Command ......我嘗試使用類型Window 嘗試綁定到View 的方法無效。 到目前為止,我最好的選擇是在 VM 上放置一個委托事件 RefreshListItems,並使用調用 DataGrid.Items.Refresh() 的函數從視圖中訂閱它。

這是可行的,但為了教育的利益,我想知道是否有人可以告訴我如何將命令屬性綁定到視圖(一個窗口)而不是視圖模型,和/或如何通知綁定到我的 ObservableCollection 的控件刷新特定的或者 ViewModel 中的所有項目沒有在每個列表項目上實現 INotifyPropertyChanged 方案?

根據mm8的建議編輯:

我在我看來為 ICommand 嘗試了您的代碼,但出現此錯誤:

System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Window', AncestorLevel='1''. BindingExpression:Path=SetService; DataItem=null; target element is 'MenuItem' (Name=''); target property is 'Command' (type 'ICommand')

這與這些嘗試中的每一個,無論RelayCommand CommandParameter ,都綁定到類型為RelayCommandDelegateCommandICommand

<MenuItem Header="Set Service" Command="{Binding SetService, RelativeSource={RelativeSource AncestorType=Window}}"/>

<MenuItem Header="Set Service" Command="{Binding SetService, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>

<MenuItem Header="Set Service" Command="{Binding SetService, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"/>

.xaml:

<Window>
…
    <DataGrid x:Name="TheGrid" ItemsSource="{Binding Source={StaticResource MessageItems}}" KeyUp="MessageList_KeyUp" AutoGenerateColumns="False" IsReadOnly="True" ColumnWidth="Auto">
    …
        <DataGrid.ContextMenu>
            <ContextMenu>
                <MenuItem Header="Set Service" Command="{Binding SetService, RelativeSource={RelativeSource AncestorType=Window}}"/>
            </ContextMenu>
        </DataGrid.ContextMenu>
    </DataGrid>
…
</Window>

.xaml.cs:

public partial class TheGridView : Window
{
    TheGridViewModel _viewModel;
    public ICommand SetService; 

    [ImportingConstructor]
    public TheGridView(TheGridViewModel vm)
    {
        DataContext = _viewModel = vm;
        vm.RefreshListItems += () => TheGrid.Items.Refresh();

        InitializeComponent();
        Closing += Window_Closing;

        SetService = new RelayCommand(SetSvc);
    }

    private void SetSvc(object selectedItem)
    {
        // Doesn't get here
    }
}

這是可行的,但為了教育的利益,我想知道是否有人可以告訴我如何將 Command 屬性綁定到View (一個Window )而不是 ViewModel?

沒有什么可以阻止您在視圖的代碼隱藏中定義ICommand屬性並像這樣綁定到它們(假設您的視圖是Window ):

Command="{Binding YourCommandProperty, RelativeSource={RelativeSource AncestorType=Window}}"

編輯:

ContextMenu駐留在它自己的可視化樹中,但您應該能夠通過DataGridTag屬性綁定到父窗口,如下所示:

<DataGrid x:Name="TheGrid" ItemsSource="{Binding Source={StaticResource MessageItems}}" KeyUp="MessageList_KeyUp" AutoGenerateColumns="False" IsReadOnly="True" ColumnWidth="Auto">
    <DataGrid.Tag>
        <Binding Path="." RelativeSource="{RelativeSource AncestorType=Window}" />
    </DataGrid.Tag>
    <DataGrid.ContextMenu>
        <ContextMenu>
            <MenuItem Header="Set Service" Command="{Binding  PlacementTarget.Tag.SetService, RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
        </ContextMenu>
    </DataGrid.ContextMenu>
</DataGrid>

...和/或如何通知綁定到我的ObservableCollection的控件刷新 ViewModel 中的特定項或所有項,而無需在每個列表項上實現INotifyPropertyChanged方案?

您不能這樣做,除非您刷新整個控件(例如使用DataGrid.Items.Refresh() )。 這就是為什么你應該實現INotifyPropertyChanged

如果您當前綁定到跨多個模塊共享的某個類並且您不想修改此類,則可以創建一個新的客戶端特定包裝類,該類實現了INotifyPropertyChanged並綁定到這個類,而不是綁定到公共類,例如:

public class Wrapper : INotifyPropertyChanged
{
    private readonly SharedModel _model;
    public Wrapper(SharedModel model)
    {
        _model = model;
    }

    private string _property;
    public string MyProperty
    {
        get { return _property; }
        set { _property = value; OnPropertyChanged(); }
    }

    //...
    public event PropertyChangedEventHandler PropertyChanged;
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM