C# 如何仅从ViewModel以编程方式高亮显示DataGrid行?

C# 如何仅从ViewModel以编程方式高亮显示DataGrid行?,c#,wpf,mvvm,data-binding,C#,Wpf,Mvvm,Data Binding,SelectedItem属性绑定不会导致在初始加载时突出显示其DataGrid行。 我有一个DataGrid,它在SelectedItem上有一个绑定,在我再次单击它之前不会突出显示。我想我有一个操作顺序的问题——这是因为所有的ViewModel代码都是在视图渲染之前运行的。只要我单击一行(即使是SelectedAccount属性中已经存在的同一行),它就可以正常工作,但我需要能够突出显示VM中的一行 我可以很容易地验证SelectedAccount属性是否为null,因为还有其他ViewMod

SelectedItem属性绑定不会导致在初始加载时突出显示其DataGrid行。

我有一个DataGrid,它在SelectedItem上有一个绑定,在我再次单击它之前不会突出显示。我想我有一个操作顺序的问题——这是因为所有的ViewModel代码都是在视图渲染之前运行的。只要我单击一行(即使是SelectedAccount属性中已经存在的同一行),它就可以正常工作,但我需要能够突出显示VM中的一行

我可以很容易地验证SelectedAccount属性是否为null,因为还有其他ViewModels通过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
事件触发两次,一次是在加载视图之前,一次是在上述攻击之后。这会触发一个SQL调用,所以我想避免这种情况。我还认为MVVM的目的是将视图与viewmodel解耦,但也许我没有像我想象的那样理解这一点

以下是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>
编辑 这个问题很微妙,但现在有了完美的解释

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);
}
private Account\u selected Account;
公共帐户选定帐户
{
get=>\您选择的账户;
设置=>SetSelectedAccount(值);
}
私人作废设置选定账户(账户价值)
{
_selectedAccount=值;

在视图模型中,使用框架的
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;
        }
    }
}
private void DataGrid\u SelectionChanged(对象发送方,selectionchangedventargs e)
{    
对于(int i=0;i
在我的示例中,我使用了一个通用的名称字符串列表,因此您可能需要将行
TextBlock cellContent=DataGrid.Columns[0].GetCellContent(行)更改为TextBlock;
cellContent.Text.Equals(DataGrid.SelectedItem))
以满足行选择标准


如果您不想使用代码隐藏,另一种选择是附加的行为,它或多或少会做相同的事情。

实现
INotifyPropertyChanged
应该足够了,这段代码在我这边有效,我正在使用
命令调用
Load()
方法,但您的代码中可能不需要它

ViewModel和C#代码:

公共部分类主窗口:窗口
{
公共主窗口()
{
初始化组件();
DataContext=新的ViewModel();
}
}
公共类视图模型:INotifyPropertyChanged
{
公共事件属性更改事件处理程序属性更改;
公共视图模型()
{
账户=新列表();
Accounts.AddRange(
可枚举范围(0,10)
.选择(r=>新帐户
{
AccountNumber=r,
FirstName=$“First{r}”,
LastName=$“Last{r}”
}));
LoadedCommand=新WpfCommand((参数)=>Load());
}
专用空心荷载()
{
SelectedAccount=Accounts.FirstOrDefault(a=>a.AccountNumber==2);
}
公共WpfCommand LoadedCommand{get;set;}
公共列表帐户{get;set;}
私人账户_selectedAccount=null;
公共帐户选定帐户
{
获取{return\u selectedAccount;}
设置
{
_selectedAccount=值;
PropertyChanged?.Invoke(这是新的PropertyChangedEventArgs(名称)(SelectedAccount));
}
}
}
公共类帐户
{
public int AccountNumber{get;set;}
公共字符串名{get;set;}
公共字符串LastName{get;set;}
}
公共类WpfCommand:ICommand
{
私人行动——执行;
私人职能执行;
公共事件处理程序CanExecuteChanged;
公共WpfCommand(操作执行,Func canExecute=null)
{
_执行=执行;
_canExecute=canExecute;
}
public void Execute(对象参数)
{
_执行?.Invoke(参数);
}
公共布尔CanExecute(对象参数)
{
返回_canExecute?.Invoke(参数)?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>

xmlns:i=”http://schemas.microsoft.com/expression/2010/interactivity"

您确定只调用。焦点还不够吗?如果您的viewModel实现正确,SelectedItem绑定应该可以工作。您确定它不是“InactiveSelectionHighlight”吗弄乱了您对突出显示的感知。@user6144226您是对的。我的视图模型中有一个错误。我为
\u selectedAccount
而不是
selectedAccount
提出了
OnPropertyChanged
。这不起作用的原因现在很明显。我可以删除所有内容,但
Focus()
来自codebehind处理程序。很好!我很感谢花时间把这一切解释清楚。它帮助我更好地理解全局。有时在一个页面上看到所有移动的部分确实更好。原来我是在提升
OnPropertyChanged(“\u selectedAccount”)
而不是
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;
    }
}
   <!--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>