Niedziałający binding w Avaloni (cross-platforma kopia WPF)

0

Cześć, mam następujący problem:

Tworzę prosty chat i wewnątrz viewModel mam 2 property:

  • SendTextBoxContent (przytrzymującą zawartość TextBoxa, do którego wpisujemy co chcemy wysłać)
  • MessageWindowContent (przetrzymująca odebrane wiadomości)

(Fragment MainWindowViewModel, który przetrzymuje oba pola)

public class MainWindowViewModel : ViewModelBase
    {
// Kod...

 public string MessageWindowContent
        {
            get => _messageWindowContent;
            set => this.RaiseAndSetIfChanged(ref _messageWindowContent, value);
        }

        public string SendTextBoxContent
        {
            get => _sendTextBoxContent;
            set => this.RaiseAndSetIfChanged(ref _sendTextBoxContent, value);
        }
/ / Kod
}

(Klasa ViewModelBase sama w sobie jest pusta, dziedziczy jedynie po ReacitveUI.)

Problem w tym, że kiedy wpisuję do kontrolki jakiś tekst, to odpowiadające property nie jest aktualizowane pomimo powiązania.

(Fragment pliku .axaml)

<TextBox
                    Name="SendTextBox"
                    Text="{Binding SendTextBoxContent}" <---- IDE podpowiada, że Property zostało poprawnie wykryte
                    Grid.Column="0"
                    Background="{DynamicResource InputBoxColor1}"
                    Foreground="{DynamicResource FontColor2}" />

Program się kompiluje, ale kiedy próbuję coś wysłać, to wartość SendTextBoxContent jest pobierana jako "":

(Metoda odpowiadająca za wysyłanie wiadomości [wstępna wersja])

public void SendAction()
        {
            MessageWindowContent += "Me: " + SendTextBoxContent + "\n"; <---- SendTextBoxContent nieprawidłowo pobierana jako pusty string
            _messenger.SendMessage(SendTextBoxContent + "\n");
            SendTextBoxContent = "";
        }

Żeby było ciekawiej, to w tej samej aplikacji mam również okienko logowania, które działa na identycznej zasadzie i wartości są sczytywane poprawnie:

(Fragment LoginWindowViewModel)

 public class LoginWindowViewModel : ViewModelBase
    {
        // Kod
        public string ServerAddress
        {
            get => _serverAddress ?? "";
            set => this.RaiseAndSetIfChanged(ref _serverAddress, value);
        }

        public string LoginName
        {
            get => _loginName ?? "";
            set => this.RaiseAndSetIfChanged(ref _loginName, value); 
        }
}

(Fragment pliku .axaml LoginWindow)

                    Name="PortText"
                    Background="{DynamicResource InputBoxColor1}"
                    Foreground="{DynamicResource FontColor2}"
                    Text="{Binding Port}" <-- Ponownie, IDE wskazuje, że binding jest poprawny
                />

Nie rozumiem, dlaczego pomimo praktycznie identycznej implementacji, w jednym okienku wszystko działa poprawnie, a w drugim nie działa w ogóle. :/
Z góry dziękuję za pomoc. :)

PS. Avalonia jest bardzo podobna do WPF, więc jeżeli nie pracowałeś/aś z Avalonią, ale masz doświadczenie z WPF to i tak pisz śmiało.

0

A spróbuj ustawić jeszcze mode na OneWay. Ostatecznie według dokumentacji możesz zaimplementować INotifyPropertyChanged zamiast używać RaiseAndSetIfChanged

      public event PropertyChangedEventHandler PropertyChanged;
      public string Name
      {
          get { return name; }
          set
          {
              name = value;
              OnPropertyChanged("Name");
          }
      }

      protected void OnPropertyChanged([CallerMemberName] string name = null)
      {
          PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
      }
0

@calineczka: A jak wygląda ViewModelBase? Dziedziczysz po ReactiveObject?

0

Ustaw np. w konstruktorze wartość domyślną dla _sendTextBoxContent zobacz czy po uruchomieniu aplikacji ta wartość domyślna wyświetli się w TextBox

1

Niestety błędy w ewentualnym binding nie wywołują Exception przez co czasem ciężko jest je zdebugować.
Włącz aplikację i sprawdź czy w output window nie masz błędu w stylu:

System.Windows.Data Error: 5 : Value produced by BindingExpression is not valid for target property.; Value='-1' BindingExpression:Path=DisplayIndex; DataItem='DataGridTextColumn' (HashCode=38001806); target element is 'TextBox' (Name=''); target property is 'Column' (type 'Int32')

Tutaj można dostrzeć czy jest jakiś problem.

Czasem binding można zepsuć przypisując wartość do do property z którą się bindujesz. Może w kodzie na inicjalizacji, albo na starcie masz coś w stylu:
SendTextBox.Text= "costam"
?

0

@HieronimBerbelek: Ani jednego błędu lub choćby ostrzeżenia. Jak na złość, akurat wtedy kiedy ich potrzebuję :|. Jeżeli chodzi o psucie przypisywaniem wartości to też raczej nie. Jedyny moment, w którym tak naprawdę dotykam proporties w kodzie, to ten w SendAction.

@gosc_z_pytaniem Nope, nic to nie daje.

@jarzi To jest zupełnie pusta bieda-klasa która jedyne co robi to dziedziczy po ReactiveObject.
Wrzucam source, może się przyda :)

 public class ViewModelBase : ReactiveObject
    {
    }

@gswidwa1 Ustawianie na OneWay nic nie daje. Spróbowałam implementacji, ale również nie działa.

0

@calineczka a próbowałaś z INotifyPropertyChanged jak napisałem?

0

@gswidwa1:

Spróbowałam implementacji, ale również nie działa.

Tak.

0

@gswidwa1:

Fragment klasy MainWindowViewModel (dziedziczy z ViewModelBase):

public string MessageWindowContent
        {
            get => _messageWindowContent;
            set
            {
                // this.RaiseAndSetIfChanged(ref _messageWindowContent, value);
                _messageWindowContent = value;
                OnPropertyChanged(nameof(MessageWindowContent));
            }
        }

        public string SendTextBoxContent
        {
            get => _sendTextBoxContent;
            set
            {
                // this.RaiseAndSetIfChanged(ref _sendTextBoxContent, value);
                _sendTextBoxContent = value;
                OnPropertyChanged(nameof(SendTextBoxContent));
            }
        }

Klasa ViewModelBase:

 public class ViewModelBase : ReactiveObject 
    {
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged(string name = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }
    }

0

Ok. ReactiveObject dziedziczy po INotifyPropertyChanged.
A więc trop chwycił mnie za sam ViewModel. Pokaż, gdzie tworzysz MainWindowViewModel

0

@gswidwa1:


            MainWindowViewModel mainWindowController = new()
            {
                ServerAddress = loginWindowController.ServerAddress,
                LoginName = loginWindowController.LoginName,
                Port = int.Parse(loginWindowController.Port),
            };

            MainWindow mainWindow = new(ref mainWindowController);
            app.Run(mainWindow);

1 użytkowników online, w tym zalogowanych: 0, gości: 1