Jak powiązać odwrotne właściwości boolowskie w WPF?

377

Mam obiekt, który ma IsReadOnlywłaściwość. Jeśli ta właściwość jest prawdziwa, chciałbym ustawić IsEnabledwłaściwość przycisku (na przykład) na false.

Chciałbym wierzyć, że mogę to zrobić tak łatwo, IsEnabled="{Binding Path=!IsReadOnly}"ale to nie działa z WPF.

Czy jestem zmuszony przejść przez wszystkie ustawienia stylu? Wydaje się po prostu zbyt pracowity dla czegoś tak prostego, jak ustawienie jednego bool na odwrotność innego bool.

<Button.Style>
    <Style TargetType="{x:Type Button}">
        <Style.Triggers>
            <DataTrigger Binding="{Binding Path=IsReadOnly}" Value="True">
                <Setter Property="IsEnabled" Value="False" />
            </DataTrigger>
            <DataTrigger Binding="{Binding Path=IsReadOnly}" Value="False">
                <Setter Property="IsEnabled" Value="True" />
            </DataTrigger>
        </Style.Triggers>
    </Style>
</Button.Style>
Russ
źródło
eh ms zrobić coś dobrego, ale nieukończone
user1005462

Odpowiedzi:

488

Możesz użyć ValueConverter, który odwraca dla ciebie właściwość bool.

XAML:

IsEnabled="{Binding Path=IsReadOnly, Converter={StaticResource InverseBooleanConverter}}"

Przetwornik:

[ValueConversion(typeof(bool), typeof(bool))]
    public class InverseBooleanConverter: IValueConverter
    {
        #region IValueConverter Members

        public object Convert(object value, Type targetType, object parameter,
            System.Globalization.CultureInfo culture)
        {
            if (targetType != typeof(bool))
                throw new InvalidOperationException("The target must be a boolean");

            return !(bool)value;
        }

        public object ConvertBack(object value, Type targetType, object parameter,
            System.Globalization.CultureInfo culture)
        {
            throw new NotSupportedException();
        }

        #endregion
    }
Chris Nicol
źródło
8
Jest kilka rzeczy, które muszę tutaj rozważyć, które prawdopodobnie spowodują, że wybiorę odpowiedź @ Paula w tej kwestii. Sam piszę (na razie), więc muszę wybrać rozwiązanie, które zapamięta „ja”, z którego będę korzystać wielokrotnie. Wydaje mi się również, że im mniej nieprzyjemne jest coś, tym lepiej, a tworzenie własności odwrotnej jest bardzo wyraźne, co ułatwia mi zapamiętanie, a także przyszłego twórcy (Mam nadzieję, Mam nadzieję), aby móc szybko zobaczyć, co ja robił, a także ułatwiał im wrzucenie mnie pod przysłowiowy autobus.
Russ
17
Według własnych argumentów, IMHO rozwiązanie konwertera jest lepsze w dłuższej perspektywie: wystarczy napisać konwerter tylko raz, a następnie można go wielokrotnie używać. Jeśli wybierzesz nową nieruchomość, będziesz musiał przepisać ją w każdej klasie, która jej potrzebuje ...
Thomas Levesque
51
Używam tego samego podejścia ... ale to sprawia, że ​​panda saaad ... = (
Max Galkin
27
W porównaniu z !tym, to jest trochę skomplikowany kod ... Ludzie wkładają wiele wysiłku, aby oddzielić to, co uważają za „kod” od tych biednych projektantów. Dodatkowe bolesne, gdy jestem zarówno programistą, jak i projektantem.
Roman Starkov
10
wiele osób, w tym ja, uważałoby to za doskonały przykład nadmiaru inżynierii. Sugeruję użycie odwróconej własności jak w poście Paula Alexandra poniżej.
Christian Westman
99

Czy rozważałeś IsNotReadOnlynieruchomość? Jeśli powiązanym obiektem jest ViewModel w domenie MVVM, wówczas dodatkowa właściwość ma idealny sens. Jeśli jest to bezpośredni model encji, możesz rozważyć kompozycję i zaprezentowanie w formularzu specjalistycznego modelu ViewModel swojej encji.

Paul Alexander
źródło
5
Właśnie rozwiązałem ten sam problem za pomocą tego podejścia i zgadzam się, że jest nie tylko bardziej elegancki, ale o wiele łatwiejszy w utrzymaniu niż przy użyciu Konwertera.
alimbada
28
Nie zgadzam się, że takie podejście jest lepsze niż konwerter wartości. Tworzy również więcej kodu, jeśli potrzebujesz kilku instancji NotProperty.
Thiru
25
MVVM nie polega na nie pisaniu kodu, ale na deklaracyjnym rozwiązywaniu problemów. W tym celu konwerter jest właściwym rozwiązaniem.
Jeff
14
Problem z tym rozwiązaniem polega na tym, że jeśli masz 100 obiektów, musisz dodać właściwość IsNotReadOnly do wszystkich 100 obiektów. Właściwość ta musiałaby być DependencyProperty. To dodaje około 10 linii kodu do wszystkich 100 obiektów lub 1000 linii kodu. Konwerter składa się z 20 linii kodu. 1000 linii lub 20 linii. Które byś wybrał?
Rhyous
8
Powszechnie mówi się o tym: zrób to raz, zrób to dwa razy, a następnie zautomatyzuj. Wątpię, użyłbym tej odpowiedzi za pierwszym razem, gdy jest potrzebna w projekcie, a następnie, jeśli wszystko będzie rosło, użyłbym zaakceptowanej odpowiedzi. Ale posiadanie gotowego fragmentu konwertera może znacznie utrudnić jego użycie.
heltonbiker
71

W przypadku standardowych wiązań należy używać konwerterów, które wyglądają na nieco wietrzne. Polecam więc przyjrzeć się mojemu projektowi CalcBinding , który został opracowany specjalnie w celu rozwiązania tego problemu i kilku innych. Dzięki zaawansowanemu wiązaniu możesz pisać wyrażenia o wielu właściwościach źródłowych bezpośrednio w xaml. Powiedz, możesz napisać coś takiego:

<Button IsEnabled="{c:Binding Path=!IsReadOnly}" />

lub

<Button Content="{c:Binding ElementName=grid, Path=ActualWidth+Height}"/>

lub

<Label Content="{c:Binding A+B+C }" />

lub

<Button Visibility="{c:Binding IsChecked, FalseToVisibility=Hidden}" />

gdzie A, B, C, IsChecked - właściwości viewModel i będzie działać poprawnie

Alex141
źródło
6
Chociaż QuickConverter jest bardziej wydajny, uważam, że tryb CalcBinding jest czytelny - użyteczny.
xmedeko
3
To świetne narzędzie. Chciałbym, żeby istniało 5 lat temu!
jugg1es
Genialne narzędzie, ale wpada w styl. <Setter.Value><cb:Binding Path="!IsReadOnly" /></Setter.Value>dostaje „Wiązanie” nie jest poprawne dla błędu kompilacji Setter.Value ”
mcalex
21

Polecam korzystanie z https://quickconverter.codeplex.com/

Odwracanie wartości logicznej jest wtedy tak proste, jak: <Button IsEnabled="{qc:Binding '!$P', P={Binding IsReadOnly}}" />

To przyspiesza czas potrzebny na napisanie konwerterów.

Noxxys
źródło
18
Podając komuś wartość -1, dobrze byłoby wyjaśnić, dlaczego.
Noxxys,
16

Chciałem, aby moja XAML pozostała tak elegancka, jak to możliwe, dlatego stworzyłem klasę do zawijania bool, który znajduje się w jednej z moich bibliotek współdzielonych, niejawne operatory pozwalają na użycie klasy jako bool w kodowaniu bezproblemowo

public class InvertableBool
{
    private bool value = false;

    public bool Value { get { return value; } }
    public bool Invert { get { return !value; } }

    public InvertableBool(bool b)
    {
        value = b;
    }

    public static implicit operator InvertableBool(bool b)
    {
        return new InvertableBool(b);
    }

    public static implicit operator bool(InvertableBool b)
    {
        return b.value;
    }

}

Jedyne zmiany potrzebne w projekcie to spowodowanie, że właściwość, którą chcesz odwrócić, zwraca to zamiast bool

    public InvertableBool IsActive 
    { 
        get 
        { 
            return true; 
        } 
    }

A w postfiksie XAML powiązanie z wartością lub odwróceniem

IsEnabled="{Binding IsActive.Value}"

IsEnabled="{Binding IsActive.Invert}"
jevansio
źródło
1
Minusem jest to, że musiałbyś zmienić cały kod, który porównał go z / przypisał go do innych boolwyrażeń typu / zmiennych, nawet nie odwołując się do wartości odwrotnej. Zamiast tego dodam metodę rozszerzenia „Nie” do pliku Boolean Struct.
Tom
1
Doh! Nieważne. Zapomniałeś musiał być Propertyw porównaniu Methoddo Binding. Moje oświadczenie „minus” nadal obowiązuje. Przy okazji metoda rozszerzenia „Boolean” „Not” jest nadal przydatna do unikania „!” Operator, którego łatwo pominąć, gdy (jak to często bywa) jest osadzony obok znaków, które wyglądają tak (tj. Jeden / więcej „(” i „l” i „I”).
Tom
10

Ten działa również dla zerowalnych boolów.

 [ValueConversion(typeof(bool?), typeof(bool))]
public class InverseBooleanConverter : IValueConverter
{
    #region IValueConverter Members

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (targetType != typeof(bool?))
        {
            throw new InvalidOperationException("The target must be a nullable boolean");
        }
        bool? b = (bool?)value;
        return b.HasValue && !b.Value;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return !(value as bool?);
    }

    #endregion
}
Andreas
źródło
4

Dodaj jeszcze jedną właściwość do modelu widoku, która zwróci wartość odwrotną. I przywiąż to do przycisku. Lubić;

w widoku modelu:

public bool IsNotReadOnly{get{return !IsReadOnly;}}

w xaml:

IsEnabled="{Binding IsNotReadOnly"}
MMM
źródło
1
Świetna odpowiedź. Jedną rzeczą do dodania jest to, że lepiej ustawić zdarzenie PropertyChanged dla IsNotReadOnly w seterze dla właściwości IsReadOnly. Dzięki temu upewnisz się, że interfejs użytkownika został poprawnie zaktualizowany.
Muhannad,
To powinna być zaakceptowana odpowiedź, ponieważ jest najprostsza.
gabnaim
2

Nie wiem, czy dotyczy to XAML, ale w mojej prostej aplikacji Windows utworzyłem powiązanie ręcznie i dodałem moduł obsługi zdarzeń formatu.

public FormMain() {
  InitializeComponent();

  Binding argBinding = new Binding("Enabled", uxCheckBoxArgsNull, "Checked", false, DataSourceUpdateMode.OnPropertyChanged);
  argBinding.Format += new ConvertEventHandler(Binding_Format_BooleanInverse);
  uxTextBoxArgs.DataBindings.Add(argBinding);
}

void Binding_Format_BooleanInverse(object sender, ConvertEventArgs e) {
  bool boolValue = (bool)e.Value;
  e.Value = !boolValue;
}
Simon Dobson
źródło
1
Wydaje się prawie takie samo jak podejście konwertera. Formata Parsezdarzenia w powiązaniach WinForm są w przybliżeniu równoważne konwerterowi WPF.
Alejandro
2

Miałem problem z inwersją, ale dobre rozwiązanie.

Motywacją było to, że projektant XAML pokazywałby pustą kontrolkę, np. Gdy nie istniał żaden MyValuestekst / brak (itemsource).

Kod początkowy: ukryj kontrolę, gdy MyValuesjest pusty. Ulepszony kod: pokaż kontrolę, gdy MyValuesNIE jest pusta lub pusta.

Oczywiście problemem jest sposób wyrażenia „1 lub więcej przedmiotów”, co jest przeciwieństwem 0 przedmiotów.

<ListBox ItemsSource={Binding MyValues}">
  <ListBox.Style x:Uid="F404D7B2-B7D3-11E7-A5A7-97680265A416">
    <Style TargetType="{x:Type ListBox}">
      <Style.Triggers>
        <DataTrigger Binding="{Binding MyValues.Count}">
          <Setter Property="Visibility" Value="Collapsed"/>
        </DataTrigger>
      </Style.Triggers>
    </Style>
  </ListBox.Style>
</ListBox>

Rozwiązałem to, dodając:

<DataTrigger Binding="{Binding MyValues.Count, FallbackValue=0, TargetNullValue=0}">

Ergo ustawia wartość domyślną dla wiązania. Oczywiście to nie działa na wszystkie rodzaje odwrotnych problemów, ale pomogło mi z czystym kodem.

EricG
źródło
2

💡 .Net Rdzeń Rozwiązanie 💡

Obsługuje sytuację zerową i nie zgłasza wyjątku, ale zwraca wartość, truejeśli nie zostanie podana żadna wartość; w przeciwnym razie pobiera wprowadzony Boolean i odwraca go.

public class BooleanToReverseConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
     => !(bool?) value ?? true;

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
     => !(value as bool?);
}

Xaml

IsEnabled="{Binding IsSuccess Converter={StaticResource BooleanToReverseConverter}}"

App.Xaml Lubię umieszczać całą moją statystykę konwertera w pliku app.xaml, więc nie muszę ich ponownie konfigurować w oknach / stronach / kontrolkach projektu.

<Application.Resources>
    <converters:BooleanToReverseConverter x:Key="BooleanToReverseConverter"/>
    <local:FauxVM x:Key="VM" />
</Application.Resources>

Dla jasności converters:jest to przestrzeń nazw rzeczywistej implementacji klasy ( xmlns:converters="clr-namespace:ProvingGround.Converters").

ΩmegaMan
źródło
1

Po odpowiedzi @ Paula napisałem w ViewModel:

public bool ShowAtView { get; set; }
public bool InvShowAtView { get { return !ShowAtView; } }

Mam nadzieję, że posiadanie fragmentu kodu pomoże komuś, prawdopodobnie nowicjuszowi, tak jak ja.
A jeśli popełnisz błąd, daj mi znać!

BTW, zgadzam się również z komentarzem @heltonbiker - to zdecydowanie poprawne podejście tylko wtedy , gdy nie musisz go używać więcej niż 3 razy ...

Ofaim
źródło
2
Nie jest to pełna właściwość i brak „OnPropertyChanged” nie zadziała. W zależności od przypadku używam 1. lub 2. odpowiedzi. Chyba że używasz frameworka takiego jak Prism, w którym frameowkr wie, kiedy zaktualizować właściwości „referencji”. Potem jest losowanie między użyciem czegoś takiego, co zasugerowałeś (ale z pełną właściwością), a odpowiedzią 1
Oyiwai
1

Zrobiłem coś bardzo podobnego. Swoją własność utworzyłem za scenami, które umożliwiały wybór skrzynki TYLKO, jeśli zakończy wyszukiwanie danych. Kiedy moje okno pojawia się po raz pierwszy, uruchamia komendę asynchronicznie ładowaną, ale nie chcę, aby użytkownik klikał kombinację podczas ładowania danych (byłby pusty, a następnie zostałby zapełniony). Więc domyślnie właściwość jest fałszywa, więc zwracam odwrotność w getterze. Następnie podczas wyszukiwania ustawiam właściwość na true, a po zakończeniu ponownie ustawiam na false.

private bool _isSearching;
public bool IsSearching
{
    get { return !_isSearching; }
    set
    {
        if(_isSearching != value)
        {
            _isSearching = value;
            OnPropertyChanged("IsSearching");
        }
    }
}

public CityViewModel()
{
    LoadedCommand = new DelegateCommandAsync(LoadCity, LoadCanExecute);
}

private async Task LoadCity(object pArg)
{
    IsSearching = true;

    //**Do your searching task here**

    IsSearching = false;
}

private bool LoadCanExecute(object pArg)
{
    return IsSearching;
}

Następnie dla combobox mogę go powiązać bezpośrednio z IsSearching:

<ComboBox ItemsSource="{Binding Cities}" IsEnabled="{Binding IsSearching}" DisplayMemberPath="City" />
GregN
źródło
0

Używam podobnego podejścia, jak @Ofaim

private bool jobSaved = true;
private bool JobSaved    
{ 
    get => jobSaved; 
    set
    {
        if (value == jobSaved) return;
        jobSaved = value;

        OnPropertyChanged();
        OnPropertyChanged("EnableSaveButton");
    }
}

public bool EnableSaveButton => !jobSaved;
soulflyman
źródło