Привязка WPF не работает при использовании другой модели данных

Я создаю приложение, которое отслеживает несколько серверов трассировки на удаленных компьютерах и отображает обработанные данные в пользовательском интерфейсе. Когда я использую симулятор, генерирующий данные, все работает так, как ожидалось. Проблема в том, что пользовательский интерфейс не обновляется при использовании фактической модели данных. Иногда он обновляется один раз после запуска, иногда никогда. ViewModel нетронута, данные правильно обработаны и доступны. Это влияет на все элементы управления, использующие привязку в XAML. Другие элементы управления, которые обновляются в коде, работают должным образом. Все ViewModels правильно реализованы INotifyPropertyChanged.

См. следующий пример:

Пользовательский контроль TimeStrip

В этом TimeStrip элементе управления текстовое поле и линия со временем должны перемещаться вправо.

XAML пользовательского управления:

<Grid  x:Name = "TimeSlotGrid">
         //..
    <local:TimeSlot Grid.Column = "2" Text = "8:00"/>
    <local:TimeSlot Grid.Column = "3" Text = "9:00"/>
        //...
</Grid>          
<Grid LayoutUpdated = "Grid_LayoutUpdated" Loaded = "Grid_Loaded">
    <Canvas x:Name = "TimeCanvas">
        <TextBlock Style = "{StaticResource MediumTextTheme}" 
                   Text = "{Binding Path=UiStuff.CurrentTimeString}" 
                   Canvas.Left = "{Binding Path=UiStuff.TimeTextPos}" 
                   Canvas.Top = "1"/>
        <Line x:Name = "CurrentTimeCursor" 
              X1 = "{Binding Path=UiStuff.TimePos}" 
              X2 = "{Binding Path=UiStuff.TimePos}" 
              Y1 = "0" Y2 = "360" Stroke = "Red" StrokeThickness = "2"/>
    </Canvas>
</Grid>

Серые прямоугольники (TimeSlot) обновляются с помощью Grid_LayoutUpdated, который устанавливает градиент фона в соответствии с текущим временем:

Код позади

private void Grid_LayoutUpdated(object sender, EventArgs e)
{
        MainViewModel context = DataContext as MainViewModel;
        context.UiStuff.GridWidth = TimeCanvas.ActualWidth;
        DateTime dt = context.UiStuff.CurrentTime;
        int currentHour = dt.AddHours(-6).Hour;
        double currentHourPart = dt.Minute / 60d;
        for (int i = 0; i < TimeSlotGrid.Children.Count; i++)
        {
            TimeSlot slot = TimeSlotGrid.Children[i] as TimeSlot;
            if (slot is null) continue;
            slot.Visibility = i <= currentHour ? Visibility.Visible : Visibility.Hidden;
            slot.Background = Brushes.Gray;
            if (i == currentHour)
            {
               lgb.GradientStops[1].Offset = currentHourPart;
               lgb.GradientStops[2].Offset = currentHourPart;
               slot.Background = lgb;
             }
         }
}

Модель представления (минимальная)

public class MainViewModel 
{
        public UIElements UiStuff { get; set; } 
        private readonly TraceCommunication Trace;
        
        public MainViewModel()
        {
            Trace = new TraceCommunication();
            Trace.MessageReceived += Trace_MessageReceived;
            Trace.StartConnection("10.128.251.136", "9334");
        }
        
        private void Trace_MessageReceived(DataRow message)
        {
            if (!DateTime.TryParse(values[Columns.TimeStamp].ToString(), out DateTime dt)) return;

            UiStuff.CurrentTime = dt;
        }
}

public class UIElements : INotifyPropertyChanged
{
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged([CallerMemberName] string name = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }
        
        private DateTime currentTime;
        public DateTime CurrentTime
        {
            get => currentTime;
            set
            {
                currentTime = value;
                CurrentTimeString = currentTime.ToString("HH:mm");
                double timescale = currentTime.AddHours(-6).TimeOfDay.TotalMinutes / 1440;
                TimePos = (int)(GridWidth * timescale);
                OnPropertyChanged();
            }
        }
        
        private string currenTimeString;

        public string CurrentTimeString
        {
            get => currenTimeString;
            set { currenTimeString = value; OnPropertyChanged();}
        }   
}

Как видно на скриншоте, привязки обновились только один раз после запуска (9:51). TimeSlots отображаются правильно (сейчас 10:30).

Как уже упоминалось, все это работает при использовании симулятора. Единственная разница заключается в объекте TraceCommunication, который контролирует удаленную систему. ViewModel подписывается на событие MessageReceived и соответствующим образом устанавливает элементы пользовательского интерфейса.

public class TraceCommunication
{
        internal delegate void TraceEventHandler(DataRow value);
        internal event TraceEventHandler MessageReceived;
        
        private TraceEngineIntf traceEngine;

        private string _Ip;
        private string _Port; 
        
        public TraceCommunication()
        {
            //...
        }

        internal bool StartConnection(string ip, string port)
        {
            _Ip = ip;
            _Port = port;
            bool success = true;
            if (!ip.Contains("."))
            {
                TraceSimulator Sim = new TraceSimulator(ip);
                Sim.MessageReceived +=  FireEventIfRelevant;
                Sim.StartTracing();
            }
            else
            {
                traceEngine = new TraceEngineIntf();
                traceEngine.ConnectedChanged += Trace_ConnectedChanged;
                traceEngine.MessageReceived += FireEventIfRelevant;
                success = traceEngine.SetHost(_Ip, _Port);
            }
            return success;
        }
        
        private void FireEventIfRelevant(DataRow row)
        {
            if (!TraceFilter.IsRelevant(row)) return;
            MessageReceived?.Invoke(row);
        }
        
        private void Trace_ConnectedChanged(object sender, EventArgs e)
        {
            traceEngine = sender as TraceEngineIntf;

            if (traceEngine.Connected)
            {
                traceEngine.StartRead();
            }
            else
            {
                Connect();
            }
        }
}

В обоих случаях данные принимаются и обрабатываются корректно. События запускаются, а затем правильно обрабатываются в ViewModel. Но пользовательский интерфейс реагирует только при использовании симулятора. Так было не всегда. Я начал использовать только настоящую вещь, которая работала нормально. Позже я реализовал симулятор, когда мне нужно было быстрее получить больше данных для разработки.

У меня нет идей, что может вызвать такое поведение. Кто-нибудь знает, что происходит, или может подсказать, что проверить дальше?

Спасибо!

В каком потоке приложение получает события?

Max Naumov 23.09.2023 11:43

@MaxNumov На самом деле я не знаю. У меня нет доступа к TraceEngineIntf, поскольку он принадлежит третьей стороне. Когда «traceEngine» подключен, он начинает читать. Я добавил это в вопрос. Мой Sim.StartTracing() — это асинхронная пустота, которая периодически запускает события.

MarcMan 23.09.2023 12:33

Вы можете проверить это с помощью отладки в своем коде. Если это не поток пользовательского интерфейса, вам следует обрабатывать обновления пользовательского интерфейса в потоке пользовательского интерфейса. В этом вопросе вы можете узнать, как проверить, нужно ли вам использовать вызов stackoverflow.com/questions/5436349/…

Max Naumov 23.09.2023 13:14

Если модели представления правильно реализуют inotifypropertychanged, то уведомление об изменении свойства будет распределяться по потокам. Я не вижу модели представления в вопросе. Много кода, но нет модели представления. Вы должны показать нам минимальное воспроизведение вашей проблемы, а не кучу кода. Я также предлагаю вам использовать snoop, чтобы проверить, что экземпляр модели представления, которая меняет свойства, действительно является контекстом данных.

Andy 23.09.2023 14:49

@Andy Энди, я добавил к вопросу ViewModel. Я также проверил контекст данных с помощью snoop, это фактический экземпляр ViewModel с измененными свойствами.

MarcMan 23.09.2023 20:38
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
5
64
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Элемент управления TimeStrip содержит 24 элемента управления TimeSlot, представляющие часы дня. Он сломался, когда я установил для BackGround текущего TimeSlot значение LinearGradientBrush вместо сплошного Brush в методе обработчика Grid_LayoutUpdatedTimeStrip.

Я исправил это, передав «currentHourPart» в качестве свойства зависимости самому TimeSlot и соответствующим образом установив там градиент.

Я до сих пор не знаю, почему это вызвало проблему и почему это сработало, когда я использовал симулятор.

Другие вопросы по теме