簡體   English   中英

如何單獨從ViewModel以編程方式突出顯示DataGrid行?

[英]How can I highlight a DataGrid row programmatically from the ViewModel alone?

SelectedItem屬性綁定不會導致其初始加載時突出顯示其DataGrid行。

我有一個帶有SelectedItem綁定的DataGrid,直到我再次單擊它才會突出顯示。 我認為我有一個操作順序問題 - 來自所有ViewModel代碼在Views被渲染之前運行的事實。 一旦我點擊一行(即使是已經在SelectedAccount道具中的那一行),它也能正常工作,但我需要能夠突出顯示VM中的一行。

我可以輕松驗證SelectedAccount屬性不為null,因為還有其他ViewModel通過PubSubEvents顯示它的值。

我已經嘗試了幾種解決方法,到目前為止我能夠讓它工作的唯一方法就是感覺很臟:

using ApplicationName.UI.ViewModels;
public partial class AccountsView : UserControl
{
    public AccountsView()
    {
        InitializeComponent();
        Loaded += AccountsView_Loaded;
    }

    private void AccountsView_Loaded(object sender, RoutedEventArgs e)
    {
        AccountsViewModel viewModel = (AccountsViewModel)DataContext;
        AccountsDataGrid.SelectedItem = viewModel.SelectedAccount;
        AccountsDataGrid.Focus();
        UpdateLayout();
    }   
}

我不喜歡這個,因為它導致OnPropertyChanged事件在視圖加載之前觸發兩次,並且在上述hack之后再次觸發。 這會觸發SQL調用,所以我想避免這種情況。 我還認為MVVM的觀點是將視圖與視圖模型分離,但也許我並不像我想的那樣理解它。

這是DataGrid的XAML:

<ScrollViewer Grid.Row="1"
              Grid.Column="0"
              Grid.ColumnSpan="3"
              Margin="5,0">
    <DataGrid Name="AccountsDataGrid"
              ItemsSource="{Binding Accounts}"
              SelectedItem="{Binding SelectedAccount}"
              AutoGenerateColumns="False"
              CanUserResizeColumns="True"
              SelectionMode="Single"
              SelectionUnit="FullRow">
        <DataGrid.Columns>
            <DataGridTextColumn Header="ClinicId" 
                                TextBlock.TextAlignment="Center"
                                Width="75"
                                Binding="{Binding ClinicId}" />
            <DataGridTextColumn Header="Account#"
                                Width="75"
                                Binding="{Binding AccountNumber}"/>
            <DataGridTextColumn Header="LastName"
                                Width="1*"
                                Binding="{Binding LastName}" />
            <DataGridTextColumn Header="FirstName"
                                Width="1*"
                                Binding="{Binding FirstName}" />
            <DataGridTextColumn Header="Balance"
                                Width="Auto"
                                Binding="{Binding Balance}" />
            <DataGridTextColumn Header="Follow Up"
                                Width="100"
                                Binding="{Binding FollowUpDate}"/>
        </DataGrid.Columns>
    </DataGrid>
</ScrollViewer>

ViewModel中的初始Load方法,我想要設置突出顯示的行。

public void Load()
{
    RefreshGrid();
    SelectedAccount = Accounts.First();
    _accountId = SelectedAccount.Id;
}

編輯

問題很微妙,但現在很有道理。

private Account _selectedAccount;

public Account SelectedAccount
{
    get => _selectedAccount;
    set => SetSelectedAccount(value);
}

private void SetSelectedAccount(Account value)
{
    _selectedAccount = value;
    OnPropertyChanged("_selectedAccount");  // <= whoops
    if (_selectedAccount != null)
        OnAccountSelected(
           _selectedAccount.PrimaryKeyFields);
}

為私有屬性引發此事件沒有意義,因為視圖無法看到它,並且綁定到SelectedAccount。 將其更改為OnPropertyChanged(“SelectedAccount”)就可以了。

實現INotifyPropertyChanged應該足夠了,這段代碼在我的最后工作,我使用Command來調用Load()方法,但是你的代碼中可能不需要它。

ViewModel和C#代碼:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        DataContext = new ViewModel();
    }
}

public class ViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public ViewModel()
    {
        Accounts = new List<Account>();

        Accounts.AddRange(
            Enumerable.Range(0, 10)
                .Select(r => new Account
                {
                    AccountNumber = r,
                    FirstName = $"First{r}",
                    LastName = $"Last{r}"
                }));

        LoadedCommand = new WpfCommand((param) => Load());
    }

    private void Load()
    {
        SelectedAccount = Accounts.FirstOrDefault(a => a.AccountNumber == 2);
    }

    public WpfCommand LoadedCommand { get; set; }

    public List<Account> Accounts { get; set; }

    private Account _selectedAccount = null;
    public Account SelectedAccount
    {
        get { return _selectedAccount; }
        set
        {
            _selectedAccount = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedAccount)));
        }
    }
}

public class Account
{
    public int AccountNumber { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

public class WpfCommand : ICommand
{
    private Action<object> _execute;
    private Func<object, bool> _canExecute;

    public event EventHandler CanExecuteChanged;

    public WpfCommand(Action<object> execute, Func<object, bool> canExecute = null)
    {
        _execute = execute;
        _canExecute = canExecute;
    }

    public void Execute(object parameter)
    {
        _execute?.Invoke(parameter);
    }

    public bool CanExecute(object parameter)
    {
        return _canExecute?.Invoke(parameter) ?? true;
    }
}

XAML:

   <!--System.Windows.Interactivity.WPF nuget package-->
   xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"

    <DataGrid 
        ItemsSource="{Binding Accounts}"
        SelectedItem="{Binding SelectedAccount}">

        <i:Interaction.Triggers>
            <i:EventTrigger EventName="Loaded">
                <i:InvokeCommandAction Command="{Binding LoadedCommand}" />
            </i:EventTrigger>
        </i:Interaction.Triggers>

        <DataGrid.Columns>
            <DataGridTextColumn Binding="{Binding AccountNumber}"/>
            <DataGridTextColumn Binding="{Binding LastName}" />
            <DataGridTextColumn Binding="{Binding FirstName}" />
        </DataGrid.Columns>
    </DataGrid>

在視圖模型中,使用框架的RaisePropertyChanged(); 功能(或等效的任何東西)。 DataGrid元素的代碼隱藏中,嘗試以下代碼:

private void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{    
    for (int i = 0; i < DataGrid.Items.Count; i++)
    {
        DataGridRow row = (DataGridRow)DataGrid.ItemContainerGenerator.ContainerFromIndex(i);
        TextBlock cellContent = DataGrid.Columns[0].GetCellContent(row) as TextBlock;
        if (cellContent != null && cellContent.Text.Equals(DataGrid.SelectedItem))
        {
            object item = DataGrid.Items[i];
            DataGrid.SelectedItem = item;
            DataGrid.ScrollIntoView(item);
            row.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
            break;
        }
    }
}

在我的示例中,我使用了一個通用的字符串名稱列表,因此您可能需要將TextBlock cellContent = DataGrid.Columns[0].GetCellContent(row) as TextBlock;的行TextBlock cellContent = DataGrid.Columns[0].GetCellContent(row) as TextBlock; cellContent.Text.Equals(DataGrid.SelectedItem))以滿足您的行選擇標准。

如果您不想使用代碼隱藏,另一種選擇是附加行為,或多或少做同樣的事情。

暫無
暫無

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

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