Я использую элемент управления CefSharp WPF в своем проекте с шаблоном MVVM. Я попытался показать внешний URL-адрес в браузере Chromium. Я использовал событие LoadingStateChanged для отслеживания события загрузки страницы в браузере Chromium. Я получаю сообщение об ошибке ниже при прослушивании события LoadingStateChanged. Не могли бы вы помочь мне решить проблему?
Сообщение об ошибке:
Вызывающий поток не может получить доступ к этому объекту, потому что другой нить владеет им.
HomeControl.xaml:
<wpf:ChromiumWebBrowser WebBrowser = "{Binding WebBrowser, Mode=OneWayToSource}" Address = "{Binding Address, Mode=OneWay}">
<i:Interaction.Triggers>
<i:EventTrigger EventName = "LoadingStateChanged">
<i:InvokeCommandAction Command = "{Binding Path=LoadingStateChangedCmd, Mode=OneWay}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</wpf:ChromiumWebBrowser>
HomeViewModel.cs:
public ICommand LoadingStateChangedCmd { get; set; }
public HomeViewModel()
{
LoadingStateChangedCmd = new CommunityToolkitInput.RelayCommand<LoadingStateChangedEventArgs>(LoadingStateChanged);
}
public void LoadingStateChanged(LoadingStateChangedEventArgs args)
{
if (args.IsLoading == false)
{
Dispatcher.CurrentDispatcher.Invoke(() =>
{
});
}
}
Подробное сообщение об ошибке:
System.InvalidOperationException HResult=0x80131509 Сообщение=The вызывающий поток не может получить доступ к этому объекту, потому что другой поток владеет им. Источник = WindowsBase StackTrace: в System.Windows.Threading.Dispatcher.VerifyAccess() в System.Windows.DependencyObject.GetValue (DependencyProperty dp) в System.Windows.Interactivity.TriggerBase.get_Actions() в System.Windows.Interactivity.TriggerBase.InvokeActions(Объект параметр) в System.Windows.Interactivity.EventTriggerBase.OnEvent(EventArgs eventArgs) в System.Windows.Interactivity.EventTriggerBase.OnEventImpl(Объект отправитель, EventArgs eventArgs) в CefSharp.Wpf.ChromiumWebBrowser.CefSharp.Internals.IWebBrowserInternal.SetLoadingStateChange(LoadingStateChangedEventArgs аргументы) в CefSharp.Internals.ClientAdapter.OnLoadingStateChange(ClientAdapter*, scoped_refptr* браузер, логическое значение isLoading, логическое значение canGoBack, логическое значение canGoForward)





Вы не сможете сделать это таким образом, потому что событие запускается в рабочем потоке, и платформа взаимодействия вызывает в ответ на него свойства зависимостей, которые нельзя вызвать из другого потока, отличного от графического интерфейса.
Обрабатывайте событие в своем представлении вручную и делайте то, что необходимо сделать напрямую, даже если это просто вызов команды на вашей виртуальной машине.
Спасибо за ваш вклад. Позвольте мне попробовать с WebView2.
Чтобы было ясно, они ведут себя одинаково в этом отношении. Преимущество WebView2 заключается в том, что это полностью поддерживаемый элемент управления WPF (а также UWP, WinForms и WinUI 2 и 3), а не предоставленный сообществом.
Если я помещаю события обратного вызова, т.е. LoadingStateChanged в код представления за файлом С#, это нарушает дизайн MVVM. Правильно ли я понимаю? Есть ли лучший подход, кроме этого?
Это не нарушает MVVM, если код в представлении является кодом просмотра, например, открытие диалогового окна сохранения при нажатии кнопки Browse рядом с текстовым полем, чтобы заполнить его, или для выполнения кода склеивания, подобного этому.
WebView2 на момент написания предоставляет только HwndHost основанную WPF реализацию CefSharp предоставляет две реализации CefSharp.Wpf собственный WPF элемент управления, он делает это путем рендеринга каждого кадра в виде растрового изображения и пересылки событий в браузер, это действительно влияет на производительность. Три тоже CefSharp.Wpf.HwndHost, что тоже HwndHost. Реализации на основе HwndHost будут иметь AirSpace проблемы, вы не сможете накладывать другие элементы управления, выполнять преобразования и т. д. См. Learn.microsoft.com/en-us/dotnet/desktop/wpf/advanced/…
Реализация CefSharp.Wpf не имеет отношения к XAML Islands. Он будет участвовать в WPF рендеринге так же, как и любой обычный WPF элемент управления. HwndHost реализации не будут корректно участвовать в WPF рендеринге (см. проблемы AirSpace). См. также github.com/MicrosoftEdge/WebView2Feedback/issues/286
LoadingStateChanged событие по умолчанию вызывается на CEF UI Thread, которое по умолчанию отличается от вашего WPF UI Thread. См. MultiThreadedMessageLoop для получения дополнительной информации по этому вопросу. Вы можете интегрировать CEF в существующий цикл сообщений вашего приложения, хотя обычно в WPF в этом нет необходимости.
В качестве альтернатив вашему текущему коду есть несколько вариантов.
Опция 1
Событие LoadingStateChangedEventArgs имеет три свойства, для использования MVVM они сопоставляются с тремя DependencyProperties, которые вы можете привязать к своему ViewModel.
Вариант № 2. Вы можете получить доступ к WebBrowser в своей модели просмотра.
// ViewModel property
private IWpfWebBrowser webBrowser;
public IWpfWebBrowser WebBrowser
{
get { return webBrowser; }
set { Set(ref webBrowser, value); }
}
<!-- XAML bind the WebBrowser property to your ViewModel -->
<cefSharp:ChromiumWebBrowser x:Name = "browser" WebBrowser = "{Binding WebBrowser, Mode=OneWayToSource}"/>
Затем вы можете subscribe/unsubscribe перейти к нужным вам событиям браузера.
Вариант №3. Вы можете подписаться на событие LoadingStateChanged в View codebind и вручную вызвать команду ViewModel.
Также не имеет отношения, но вместо этого вы должны использовать
WebView2, который предназначен для рендеринга непосредственно в стеке окон XAML. В отличие от CefBrowser, который, насколько мне известно, больше похож на остров XAML, покрывая элементы управления, которыми он должен управлять, и имеет большие последствия для производительности.