[英]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.