Como vincular propriedades booleanas inversas no WPF?


378

O que tenho é um objeto que possui uma IsReadOnlypropriedade. Se essa propriedade for verdadeira, eu gostaria de definir a IsEnabledpropriedade em um Button (por exemplo) como false.

Eu gostaria de acreditar que posso fazê-lo tão facilmente quanto IsEnabled="{Binding Path=!IsReadOnly}"isso, mas isso não ocorre com o WPF.

Estou relegado a ter que passar por todas as configurações de estilo? Apenas parece muito prolixo para algo tão simples quanto definir um bool para o inverso de outro 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>


eh ms fazer coisa boa, mas incompleta
— user1005462

Respostas:


488

Você pode usar um ValueConverter que inverte uma propriedade bool para você.

XAML:

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

Conversor:

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

8
Há algumas coisas que tenho que considerar aqui, que provavelmente me farão escolher a resposta de @ Paul sobre essa. Estou sozinho ao codificar (por enquanto), portanto, preciso usar uma solução que "eu" me lembre, que usarei repetidamente. Também acho que quanto menos prolixo for o melhor, e criar uma propriedade inversa é muito explícita, facilitando a lembrança, assim como os futuros desenvolvedores (espero, espero), de poder ver rapidamente o que estava fazendo, além de tornar mais fácil para eles me jogarem sob o proverbial ônibus.
— Russ

17
Por seus próprios argumentos, IMHO, a solução do conversor é melhor a longo prazo: você só precisa escrever o conversor uma vez e depois pode reutilizá-lo várias vezes. Se você vai para a nova propriedade, você terá que reescrevê-lo em cada classe que precisa dele ...
— Thomas Levesque

51
Eu estou usando a mesma abordagem ... mas faz panda saaad ... = (
— Max Galkin

27
Comparado a !isso, esse é um código demorado ... As pessoas enviam esforços insanos para separar o que consideram "código" daqueles pobres designers. Extra extra doloroso quando eu sou o codificador e o designer.
— Roman Starkov

10
muitas pessoas, inclusive eu, considerariam este um excelente exemplo de excesso de engenharia. Sugiro usar uma propriedade invertida como no post de Paul Alexander abaixo.
— Christian Westman

99

Você já considerou uma IsNotReadOnlypropriedade? Se o objeto que está sendo vinculado for um ViewModel em um domínio MVVM, a propriedade adicional fará todo sentido. Se for um modelo direto de entidade, considere a composição e a apresentação de um ViewModel especializado da sua entidade no formulário.


5
Acabei de resolver o mesmo problema usando essa abordagem e concordo que não apenas é mais elegante, mas muito mais sustentável do que usar um conversor.
— Alimbada

28
Eu discordo que essa abordagem é melhor que o conversor de valor. Também produz mais código se você precisar de várias instâncias NotProperty.
— Thiru

25
MVVM não é sobre não escrever código, é sobre resolver problemas declarativamente. Para esse fim, o conversor é a solução correta.
— 31412 Jeff

14
O problema com esta solução é que, se você tiver 100 objetos, precisará adicionar uma propriedade IsNotReadOnly a todos os 100 objetos. Essa propriedade teria que ser uma DependencyProperty. Isso adiciona cerca de 10 linhas de código a todos os 100 objetos ou 1000 linhas de código. O conversor é de 20 linhas de código. 1000 linhas ou 20 linhas. Qual você escolheria?
— Rhyous

8
Existe um ditado comum para isso: faça uma vez, faça duas vezes e depois automatize. Na dúvida, eu usaria essa resposta na primeira vez que for necessária em um projeto e, se as coisas crescerem, usaria a resposta aceita. Porém, ter o snippet do conversor pré-fabricado pode dificultar o uso.
— 9239 heltonbiker

71

Com a ligação padrão, você precisa usar conversores com um pouco de vento. Portanto, recomendo que você analise meu projeto CalcBinding , desenvolvido especialmente para resolver esse problema e outros. Com a ligação avançada, você pode escrever expressões com muitas propriedades de origem diretamente no xaml. Digamos, você pode escrever algo como:

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

ou

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

ou

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

ou

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

onde A, B, C, IsChecked - propriedades do viewModel e ele funcionará corretamente


6
Embora o QuickConverter seja mais poderoso, acho o modo CalcBinding legível - utilizável.
— precisa saber é o seguinte

3
Essa é uma ótima ferramenta. Eu gostaria que existisse 5 anos atrás!
— jugg1es

Ferramenta brilhante, mas cai em estilos. <Setter.Value><cb:Binding Path="!IsReadOnly" /></Setter.Value>recebe uma 'ligação' não é válido para o erro tempo de compilação Setter.Value'
— mcalex

21

Eu recomendaria usar https://quickconverter.codeplex.com/

A inversão de um booleano é tão simples quanto: <Button IsEnabled="{qc:Binding '!$P', P={Binding IsReadOnly}}" />

Isso acelera o tempo normalmente necessário para gravar conversores.


19
Ao atribuir um -1 a alguém, seria bom explicar o porquê.
— Noxxys 20/10/2014

16

Eu queria que meu XAML permanecesse o mais elegante possível, então criei uma classe para agrupar o bool que reside em uma das minhas bibliotecas compartilhadas, os operadores implícitos permitem que a classe seja usada como um bool no code-behind sem problemas

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

}

As únicas alterações necessárias ao seu projeto são fazer com que a propriedade que você deseja inverter retorne isso em vez de bool

    public InvertableBool IsActive 
    { 
        get 
        { 
            return true; 
        } 
    }

E no XAML postfix a ligação com Value ou Invert

IsEnabled="{Binding IsActive.Value}"

IsEnabled="{Binding IsActive.Invert}"

11
A desvantagem é que você precisaria alterar todo o código que o comparasse com / atribuído a outras boolexpressões / variáveis ​​de tipo, mesmo sem fazer referência ao valor inverso. Em vez disso, eu adicionaria um método de extensão "Não" ao arquivo Boolean Struct.
— Tom

11
Doh! Deixa pra lá. Esqueci tinha que ser Propertyvs. Methodpara Binding. Minha declaração "Downside" ainda se aplica. Aliás, o método de extensão "Not" 'booleano' ainda é útil para evitar o "!" Operador que é facilmente esquecido quando (como costuma ser o caso) é incorporado ao lado de caracteres que se parecem com ele (ou seja, um / mais "(" 's e "l" e "I")
— Tom

10

Este também funciona para bools anuláveis.

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

4

Adicione mais uma propriedade no seu modelo de exibição, que retornará valor reverso. E ligue isso ao botão. Gostar;

no modelo de exibição:

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

no xaml:

IsEnabled="{Binding IsNotReadOnly"}

11
Ótima resposta. Uma coisa a acrescentar: é melhor aumentar o evento PropertyChanged para IsNotReadOnly no setter da propriedade IsReadOnly. Com isso, você garantirá que a interface do usuário seja atualizada corretamente.
— Muhannad

Essa deve ser a resposta aceita, pois é a mais simples.
— gabnaim

2

Não sei se isso é relevante para o XAML, mas no meu aplicativo Windows simples, criei a ligação manualmente e adicionei um manipulador de eventos 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;
}

11
Parece praticamente o mesmo que a abordagem do conversor. Formate Parseeventos nas ligações WinForms são aproximadamente equivalentes ao conversor WPF.
— Alejandro

2

Eu tive um problema de inversão, mas uma solução pura.

A motivação foi que o designer XAML mostraria um controle vazio, por exemplo, quando não havia datacontext / no MyValues(itemssource).

Código inicial: oculta o controle quando MyValuesestá vazio. Código aprimorado: mostre o controle quando MyValuesNÃO for nulo ou vazio.

Obviamente, o problema é como expressar '1 ou mais itens', que é o oposto de 0 itens.

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

Eu o resolvi adicionando:

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

Ergo definindo o padrão para a encadernação. Claro que isso não funciona para todos os tipos de problemas inversos, mas me ajudou com um código limpo.


2

Solution .Net Core Solution 💡

Lida com a situação nula e não lança uma exceção, mas retorna truese nenhum valor for apresentado; caso contrário, pega o booleano inserido e o inverte.

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 Gosto de colocar todas as estatísticas do meu conversor no arquivo app.xaml para não precisar redeclará-las nas janelas / páginas / controles do projeto.

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

Para ser claro, converters:é o espaço para nome da implementação real da classe ( xmlns:converters="clr-namespace:ProvingGround.Converters").


1

Após a resposta de @ Paul, escrevi o seguinte no ViewModel:

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

Espero que ter um trecho aqui ajude alguém, provavelmente novato como eu.
E se houver algum erro, avise-me!

BTW, eu também concordo com o comentário @heltonbiker - é definitivamente a abordagem correta apenas se você não precisar usá-la mais de três vezes ...


2
Não sendo uma propriedade completa e sem um "OnPropertyChanged", isso não funcionará. 1 ou 2 respostas é o que estou usando, dependendo do caso. A menos que você esteja usando uma estrutura como o Prism, na qual o frameowkr sabe quando atualizar as propriedades "referidas". Então é um lance entre usando algo parecido com o que você sugeriu (mas com a propriedade plena), e resposta 1
— Oyiwai

1

Eu fiz algo muito parecido. Criei minha propriedade nos bastidores que permitia a seleção de uma caixa de combinação SOMENTE se ela terminasse de pesquisar dados. Quando minha janela aparece pela primeira vez, ele lança um comando carregado assíncrono, mas não quero que o usuário clique na caixa de combinação enquanto ainda carrega dados (estaria vazio e preenchido). Então, por padrão, a propriedade é false, então eu retorno o inverso no getter. Então, quando estou pesquisando, defino a propriedade como true e retorne para false quando concluir.

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

Em seguida, para a caixa de combinação, posso vinculá-lo diretamente ao IsSearching:

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

0

Eu uso uma abordagem semelhante como @Ofaim

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

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

public bool EnableSaveButton => !jobSaved;
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.