Dwukierunkowe wiązanie pola tekstowego do suwaka w WPF


Moim największym wyzwaniem jest znalezienie sposobu na rozwiązanie problemu, który mam z wiązaniem danych do suwaka i pola tekstowego.

Dostosowywanie

:
aktualna wartość suwaka jest wyświetlana wewnątrz pola tekstowego. Gdy użytkownik przeciągnie suwak, wartość zostanie odzwierciedlona w polu tekstowym. Użytkownik może przeciągnąć i upuścić suwak do wybranej wartości, kliknąć dowolne miejsce na ścieżce suwaka, aby ustawić wartość, lub wprowadzić wartość ręcznie w polu tekstowym. W tym drugim przypadku wartość wpisana w polu tekstowym powinna aktualizować pozycję suwaka.
Texbox jest dwukierunkowo powiązany z właściwością datacontext, podczas gdy suwak jest jednokierunkowo powiązany z tą samą właściwością. Gdy użytkownik przesuwa lub klika suwak trackera, używam zdarzenia dragcompleted suwaka, aby powiadomić kontekst danych o zmianie. Z drugiej strony, gdy użytkownik kliknie moduł śledzący, używam zdarzenia OnValueChanged suwaka, aby powiadomić kontekst danych (i używam flagi, aby upewnić się, że OnValueChanged nie został wyzwolony przez przesunięcie suwaka)

Problem
: Zdarzenie OnValueChanged jest wyzwalane nawet wtedy, gdy wartość suwaka jest inicjowana z wartością powiązania, więc nie mogę dowiedzieć się, czy ta wartość faktycznie pochodzi od użytkownika, czy z powiązania.
Czy możesz zasugerować alternatywny sposób wykonania wiązania, aby upewnić się, że możemy odróżnić aktualizację użytkownika od powiązania aktualizacji dla suwaka?
Dziękuję!

Aktualizacja

Przepraszam, zapomniałem wspomnieć, dlaczego nie łączę zarówno suwaka, jak i pola tekstowego bezpośrednio na dwa sposoby, jak sugerują poniższe odpowiedzi. Oczekuje się, że aktualizacja wartości kontekstu danych spowoduje wywołanie serwera zaplecza i pobranie danych z bazy danych. Problem polega na tym, że gdy użytkownik przeciąga suwak, powoduje on aktualizacje. Obchodzę ten problem, polegając tylko na rzeczywistym zdarzeniu onValueChanged, aby wywołać metodę DoWhthing. Mam nadzieję, że to jest trochę jaśniejsze. Przepraszam, że to pomijam ...
Szybko zebrałem próbkę poniżej, abyś mógł spróbować.

W XAML

:
<Window x:Class="SliderIssue.MainWindow"
xmlns="[url=http://schemas.microsoft.com/winfx/2006/xaml/presentation"]http://schemas.microsoft.com/w ... ot%3B[/url]
xmlns:x="[url=http://schemas.microsoft.com/winfx/2006/xaml"]http://schemas.microsoft.com/winfx/2006/xaml"[/url]
Title="MainWindow" Height="350" Width="525">
<Grid HorizontalAlignment="Center"
VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions> <Slider Name="slider" VerticalAlignment="Top"
ValueChanged="slider_ValueChanged"
Thumb.DragStarted="slider_DragStarted"
Thumb.DragCompleted="slider_DragCompleted"
Value="{Binding Count}"
Width="200"
Minimum="0"
Maximum="100"/>
<TextBox VerticalAlignment="Top"
HorizontalAlignment="Left"
Grid.Column="1"
Width="100"
Text="{Binding Count,Mode=TwoWay,UpdateSourceTrigger=LostFocus}"
Height="25"/>
</Grid>
Na kod

:
using System.Windows;namespace SliderIssue
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private bool _dragStarted; public MainWindow()
{
InitializeComponent(); var item = new Item();
DataContext = item;
} private void slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
if (!_dragStarted)
{
var item = (Item)DataContext;
item.DoWhatever(e.NewValue);
}
} private void slider_DragStarted(object sender, System.Windows.Controls.Primitives.DragStartedEventArgs e)
{
_dragStarted = true;
} private void slider_DragCompleted(object sender, System.Windows.Controls.Primitives.DragCompletedEventArgs e)
{
_dragStarted = false; var item = (Item) DataContext;
item.DoWhatever(slider.Value);
}
}
}
Prosta klasa danych

:
using System.ComponentModel;namespace SliderIssue
{
public class Item : INotifyPropertyChanged
{
private int _count = 50;
public int Count
{
get { return _count; }
set
{
if (_count != value)
{
_count = value;
DoWhatever(_count);
OnPropertyChanged("Count");
}
}
} public void DoWhatever(double value)
{
//do something with value
//and blablabla
} public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
}

Zaproszony:
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

AKTUALIZACJA:
Mniej więcej tak samo jak z Ericiem, ale jako osobna propozycja operacji.
  • Powiąż obie kontrolki, aby były traktowane jako dwustronne, jak zasugerowałem poniżej.
  • Utwórz licznik czasu, który uruchamia się co sekundę, sprawdzając dwie zmienne.
  • (Kontrola timera # 1) sprawdza, czy zapytanie do bazy danych (np. Flaga logiczna) jest uruchomione. Jeśli to prawda, to nic nie robi. Jeśli nie ma operacji (fałsz), przechodzi do kroku 4.
  • (Sprawdzanie timera nr 2) Sprawdza, czy licznik się zmienił. Jeśli liczba uległa zmianie, ustawia flagę bieżącego żądania danych (znalezioną/użytą w kroku 3), inicjuje asynchroniczne wywołanie bazy danych i kończy działanie.
  • (Calling Database Action) pobiera dane bazy danych i odpowiednio aktualizuje maszynę wirtualną. Ustawia flagę bieżącego żądania danych na wartość false, co umożliwia sprawdzanie licznika czasu rozpoczęcie nowego żądania, jeśli licznik ulegnie zmianie.

W ten sposób możesz zarządzać aktualizacjami, nawet jeśli użytkownik oszaleje.
Myślę, że za dużo o tym myślałeś. Usuń wszystkie wydarzenia z suwaka i pola tekstowego. Jeśli pierwsza wartość (ustawiana programowo) nie powinna wywoływać metody DoWhewhere, zaznacz pole w tym kodzie, aby pominąć pierwszą inicjalizację ...
Zalecam powiązanie suwaka z Count jako TwoWay i wymuszenie na właściwości Count wykonywania dowolnego procesu (jak pokazano w klasie encji). Nie ma potrzeby sprawdzania kliknięć ani żadnych innych zdarzeń. Jeśli użytkownik zmieni wartość w polu tekstowym, zmieni suwak i odwrotnie.
<Slider Name="slider"
VerticalAlignment="Top"
Value="{Binding Count, Mode=TwoWay}"
Width="200"
Minimum="0"
Maximum="100"/>
<TextBox VerticalAlignment="Top"
HorizontalAlignment="Left"
Grid.Column="1"
Width="100"
Text="{Binding Count,Mode=TwoWay,UpdateSourceTrigger=LostFocus}"
Height="25"/>
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:


UPDATE

39, teraz rozumiem, dlaczego próbowałeś to zrobić w ten sposób. Mam kilka sugestii, które mogą pomóc.


Mój pierwszy jest trochę bardziej zarozumiały, ale mimo to sugeruję. Jeśli problemem, który próbujesz rozwiązać, jest dławienie zapytań do wewnętrznej bazy danych, powiedziałbym, że Twój ViewModel nie powinien się tym martwić. Popchnąłbym to w dół warstwy do obiektu, który wywołuje zaplecze w oparciu o zaktualizowaną wartość przekazaną z ViewModel.
Możesz stworzyć próbę ograniczania przepustowości przez biedaka, pisząc
DateTimeOffset.Now
za każdym razem, gdy wywoływana jest metoda w celu wysłania zapytania do zaplecza DB. Porównaj tę wartość z ostatnio zapisaną wartością. Jeśli TimeSpan między spadnie poniżej minimalnego progu, zaktualizuj wartość, z którą został porównany, i zignoruj ​​żądanie.
Możesz zrobić to samo z timerem i zresetować timer za każdym razem, gdy zostanie wysłane żądanie, ale jest to jeszcze bardziej zagmatwane.
Gdy wywołanie powraca z zaplecza, ta warstwa wywołuje zdarzenie, które obsługuje ViewModel i robi wszystko, co musi zrobić ze zwróconymi danymi.
Jako kolejna sugestia chciałbym również sprawdzić, co daje Ci ReactiveExtensions. Zrozumienie, jak działają, zajmuje trochę czasu, ale możesz utworzyć
Observable
na podstawie przepływu zdarzenia, a następnie użyć metody
Throttle ()
, aby zwrócić kolejną
Observable
. Subskrybujesz to
Observable
i tam dzwonisz. Wymagałoby to ponownego przemyślenia projektu i architektury oprogramowania, ale jest to intrygujące.
Paul Betts stworzył cały framework MVVM oparty na Rx o nazwie

ReactiveUI
http://www.reactiveui.net... Po raz pierwszy dowiedziałem się o ograniczaniu obserwałów w jednym z jego postów na blogu

tutaj
http://blog.paulbetts.org/inde ... lper/
.
Powodzenia!

ORYGINALNY POST
Jeśli dobrze rozumiem twój problem, wygląda na to, że chciałbyś, aby zarówno suwak, jak i TextBox odzwierciedlały tę samą właściwość DataContext (zwykle ViewModel). Wygląda na to, że próbujesz zduplikować to, co daje mechanizm wiązania WPF. Udało mi się szybko uzyskać prototyp tej pracy. Oto kod, którego użyłem.
Dla widoku właśnie utworzyłem nowe okno z tym jako zawartością okna.
<StackPanel>
<Slider Value="{Binding TheValue}" Margin="16"/>
<TextBox Text="{Binding TheValue}" Margin="16"/>
</StackPanel>

Zwróć uwagę, że zarówno suwak, jak i TextBox są powiązane z tą samą (sprytnie nazwaną) wartością DataContext. Gdy użytkownik wprowadzi nową wartość do TextBox, wartość zmieni się, a powiadomienie o zmianie właściwości (w ViewModel) spowoduje, że suwak automatycznie zaktualizuje swoją wartość.
Oto kod dla ViewModel (tj. DataContext widoku).
class TextySlideyViewModel : ViewModelBase
{
private double _theValue; public double TheValue
{
get { return _theValue; }
set
{
if(_theValue == value)
return; _theValue = value;
OnPropertyChanged("TheValue");
}
}
}

Mój ViewModel pochodzi z klasy ViewModelBase, która implementuje interfejs
INotifyPropertyChanged
. Metoda
OnPropertyChanged ()
jest zdefiniowana w klasie bazowej, która po prostu wywołuje zdarzenie dla właściwości, której nazwa jest przekazywana jako parametr.
Na koniec utworzyłem View i przypisałem nową instancję ViewModel jako jego DataContext (zrobiłem to bezpośrednio w metodzie
OnStartup ()
aplikacji dla tego przykładu).
Mam nadzieję, że pomoże ci to podążać we właściwym kierunku.

Aby odpowiedzieć na pytania, Zaloguj się lub Zarejestruj się