Bagaimana cara mengikat properti boolean terbalik di WPF?

378

Apa yang saya miliki adalah objek yang memiliki IsReadOnlyproperti. Jika properti ini benar, saya ingin mengatur IsEnabledproperti pada Button, (misalnya), menjadi false.

Saya ingin percaya bahwa saya bisa melakukannya semudah IsEnabled="{Binding Path=!IsReadOnly}"tapi itu tidak terbang dengan WPF.

Apakah saya terdegradasi karena harus melalui semua pengaturan gaya? Sepertinya terlalu bertele-tele untuk sesuatu yang sederhana seperti pengaturan satu bool ke kebalikan dari bool lain.

<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
sumber
eh ms melakukan hal yang baik tetapi tidak
menyelesaikannya

Jawaban:

488

Anda bisa menggunakan ValueConverter yang membalikkan properti bool untuk Anda.

XAML:

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

Konverter:

[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
sumber
8
Ada beberapa hal yang harus saya pertimbangkan di sini, yang kemungkinan akan membuat saya memilih jawaban @ Paul atas yang ini. Saya sendiri ketika coding (untuk saat ini), jadi saya harus pergi dengan solusi yang "saya" akan ingat, yang akan saya gunakan berulang kali. Saya juga merasa bahwa sesuatu yang kurang bertele-tele adalah yang lebih baik, dan menciptakan properti terbalik sangat eksplisit, membuatnya mudah bagi saya untuk mengingat, serta pengembang masa depan (Saya Berharap, Saya Berharap), untuk dapat dengan cepat melihat apa yang saya sedang melakukan, serta membuatnya lebih mudah bagi mereka untuk membuang saya di bawah bus pepatah.
Russ
17
Dengan argumen Anda sendiri, IMHO solusi konverter lebih baik dalam jangka panjang: Anda hanya perlu menulis konverter sekali, dan setelah itu Anda dapat menggunakannya kembali berulang kali. Jika Anda pergi untuk properti baru, Anda harus menulis ulang di setiap kelas yang membutuhkannya ...
Thomas Levesque
51
Saya menggunakan pendekatan yang sama ... tapi itu membuat panda saaad ... = (
Max Galkin
27
Dibandingkan dengan !, itu adalah kode yang bertele-tele ... Orang-orang melakukan upaya gila untuk memisahkan apa yang mereka rasakan sebagai "kode" dari para desainer miskin. Ekstra ekstra menyakitkan ketika saya adalah pembuat kode dan perancang.
Roman Starkov
10
banyak orang termasuk saya akan menganggap ini sebagai contoh utama over-engineering. Saya sarankan menggunakan properti terbalik seperti pada posting Paul Alexander di bawah ini.
Christian Westman
99

Sudahkah Anda mempertimbangkan sebuah IsNotReadOnlyproperti? Jika objek yang diikat adalah ViewModel dalam domain MVVM, maka properti tambahan masuk akal. Jika model Entitas langsung, Anda dapat mempertimbangkan komposisi dan menyajikan ViewModel khusus dari entitas Anda ke formulir.

Paul Alexander
sumber
5
Saya baru saja memecahkan masalah yang sama dengan menggunakan pendekatan ini dan saya setuju bahwa tidak hanya lebih elegan, tetapi jauh lebih mudah dirawat daripada menggunakan Konverter.
alimbada
28
Saya tidak setuju bahwa pendekatan ini lebih baik daripada konverter nilai. Ini juga menghasilkan lebih banyak kode jika Anda memerlukan beberapa instance NotProperty.
Thiru
25
MVVM bukan tentang tidak menulis kode, ini tentang memecahkan masalah secara deklaratif. Untuk itu, konverter adalah solusi yang tepat.
Jeff
14
Masalah dengan solusi ini adalah bahwa jika Anda memiliki 100 objek, Anda harus menambahkan properti IsNotReadOnly ke semua 100 objek. Properti itu harus menjadi Properti Ketergantungan. Itu menambahkan sekitar 10 baris kode ke semua 100 objek atau 1000 baris kode. Konverter adalah 20 baris kode. 1000 baris atau 20 baris. Yang mana yang akan Anda pilih?
Rhyous
8
Ada pepatah umum untuk ini: lakukan sekali, lakukan dua kali, dan kemudian diotomatisasi. Dalam keraguan, saya akan menggunakan jawaban ini pertama kali dibutuhkan dalam suatu proyek, dan kemudian jika hal-hal tumbuh, saya akan menggunakan jawaban yang diterima. Tetapi memiliki potongan konverter yang dibuat sebelumnya mungkin membuatnya lebih sulit untuk digunakan.
heltonbiker
71

Dengan penjilidan standar Anda harus menggunakan konverter yang terlihat sedikit berangin. Jadi, saya sarankan Anda untuk melihat proyek saya CalcBinding , yang dikembangkan khusus untuk menyelesaikan masalah ini dan beberapa lainnya. Dengan pengikatan tingkat lanjut, Anda dapat menulis ekspresi dengan banyak properti sumber langsung di xaml. Katakanlah, Anda dapat menulis sesuatu seperti:

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

atau

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

atau

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

atau

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

di mana A, B, C, IsChecked - properti viewModel dan itu akan berfungsi dengan baik

Alex141
sumber
6
Meskipun QuickConverter lebih kuat, saya menemukan mode CalcBinding dapat dibaca - dapat digunakan.
xmedeko
3
Ini adalah alat yang hebat. Saya berharap itu ada 5 tahun yang lalu!
jugg1es
Alat yang brilian, tetapi jatuh dalam gaya. <Setter.Value><cb:Binding Path="!IsReadOnly" /></Setter.Value>mendapat 'Binding' tidak valid untuk kesalahan waktu kompilasi Setter.Value '
mcalex
21

Saya akan merekomendasikan menggunakan https://quickconverter.codeplex.com/

Membalikkan boolean kemudian sesederhana: <Button IsEnabled="{qc:Binding '!$P', P={Binding IsReadOnly}}" />

Itu mempercepat waktu yang biasanya diperlukan untuk menulis konverter.

Noxxys
sumber
19
Ketika memberikan -1 kepada seseorang, alangkah baiknya menjelaskan mengapa.
Noxxys
16

Saya ingin XAML saya tetap seanggun mungkin sehingga saya membuat kelas untuk membungkus bool yang berada di salah satu pustaka bersama saya, operator implisit memungkinkan kelas untuk digunakan sebagai bool dalam kode-belakang mulus

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;
    }

}

Satu-satunya perubahan yang diperlukan untuk proyek Anda adalah membuat properti yang ingin Anda kembalikan, bukan bool

    public InvertableBool IsActive 
    { 
        get 
        { 
            return true; 
        } 
    }

Dan pada postfix XAML pengikatan dengan Value atau Invert

IsEnabled="{Binding IsActive.Value}"

IsEnabled="{Binding IsActive.Invert}"
jevansio
sumber
1
Kelemahannya adalah Anda harus mengubah semua kode yang membandingkannya dengan / menugaskannya ke boolJenis Ekspresi / Variabel lain bahkan tidak merujuk pada nilai terbalik. Saya malah akan menambahkan Metode Ekstensi "Tidak" ke Boolean Struct.
Tom
1
Doh! Sudahlah. Lupa harus Propertyvs. Methoduntuk Binding. Pernyataan "Kelemahan" saya masih berlaku. Btw, Metode Ekstensi 'Boolean` "Tidak" masih berguna untuk menghindari "!" Operator yang mudah terjawab ketika (seperti yang sering terjadi) tertanam di sebelah karakter yang terlihat seperti itu (yaitu satu / lebih "(" dan "l" dan "aku")
Tom
10

Yang ini juga berfungsi untuk bools nullable.

 [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
sumber
4

Tambahkan satu properti lagi di model tampilan Anda, yang akan mengembalikan nilai terbalik. Dan ikat itu ke tombol. Suka;

dalam model tampilan:

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

dalam xaml:

IsEnabled="{Binding IsNotReadOnly"}
MMM
sumber
1
Jawaban yang bagus Satu hal yang perlu ditambahkan, dengan menggunakan ini, Anda lebih baik membangkitkan event PropertyChanged untuk IsNotReadOnly di setter untuk properti IsReadOnly. Dengan ini, Anda akan memastikan UI diperbarui dengan benar.
Muhannad
Ini harus menjadi jawaban yang diterima karena ini adalah yang paling sederhana.
gabnaim
2

Tidak tahu apakah ini relevan dengan XAML, tetapi dalam aplikasi Windows sederhana saya, saya membuat penjilidan secara manual dan menambahkan penangan event Format.

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
sumber
1
Sepertinya hampir sama dari pendekatan konverter. Formatdan Parseperistiwa dalam binding WinForms kira-kira setara dengan konverter WPF.
Alejandro
2

Saya punya masalah inversi, tetapi solusi yang rapi.

Motivasi adalah bahwa desainer XAML akan menunjukkan kontrol kosong misalnya ketika tidak ada datacontext / no MyValues(itemssource).

Kode awal: sembunyikan kontrol saat MyValueskosong. Kode yang ditingkatkan: tunjukkan kontrol saat MyValuesBUKAN nol atau kosong.

Ofcourse masalahnya adalah bagaimana mengekspresikan '1 item atau lebih', yang merupakan kebalikan dari 0 item.

<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>

Saya menyelesaikannya dengan menambahkan:

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

Pengaturan Ergo default untuk mengikat. Tentu ini tidak bekerja untuk semua jenis masalah terbalik, tetapi membantu saya dengan kode bersih.

EricG
sumber
2

💡. Solusi Inti Neto 💡

Menangani situasi nol dan tidak melempar pengecualian, tetapi kembali truejika tidak ada nilai yang disajikan; jika tidak, ambil Boolean yang dimasukkan dan balikkan.

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 Saya suka meletakkan semua statika konverter saya di file app.xaml jadi saya tidak perlu mendeklarasikan ulangnya di seluruh jendela / halaman / kontrol proyek.

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

Yang jelas converters:adalah namespace untuk implementasi kelas aktual ( xmlns:converters="clr-namespace:ProvingGround.Converters").

GamegaMan
sumber
1

Mengikuti jawaban @ Paul, saya menulis yang berikut di ViewModel:

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

Saya harap memiliki cuplikan di sini akan membantu seseorang, mungkin pemula seperti saya.
Dan jika ada kesalahan, beri tahu saya!

BTW, saya juga setuju dengan komentar @heltonbiker - itu pasti pendekatan yang benar hanya jika Anda tidak harus menggunakannya lebih dari 3 kali ...

Ofaim
sumber
2
Bukan properti penuh dan tidak memiliki "OnPropertyChanged" ini tidak akan berhasil. Jawaban 1 atau 2 adalah apa yang saya gunakan, tergantung pada kasusnya. Kecuali jika Anda menggunakan kerangka kerja seperti Prism di mana frameowkr tahu kapan harus memperbarui properti "dimaksud". Maka itu adalah melemparkan antara menggunakan sesuatu seperti apa yang Anda sarankan (tetapi dengan properti penuh), dan jawaban 1
Oyiwai
1

Saya melakukan sesuatu yang sangat mirip. Saya membuat properti saya di belakang layar yang memungkinkan pemilihan combobox ONLY jika sudah selesai mencari data. Ketika jendela saya pertama kali muncul, ia meluncurkan perintah yang dimuat async tapi saya tidak ingin pengguna mengklik combobox saat masih memuat data (akan kosong, maka akan diisi). Jadi, secara default, properti itu salah jadi saya mengembalikan invers di pengambil. Lalu ketika saya mencari saya mengatur properti ke true dan kembali ke false ketika selesai.

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;
}

Kemudian untuk kotak kombo saya dapat mengikatnya langsung ke IsSearching:

<ComboBox ItemsSource="{Binding Cities}" IsEnabled="{Binding IsSearching}" DisplayMemberPath="City" />
GregN
sumber
0

Saya menggunakan pendekatan serupa seperti @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
sumber