Làm thế nào để liên kết các thuộc tính boolean nghịch đảo trong WPF?


378

Những gì tôi có là một đối tượng có một IsReadOnlytài sản. Nếu thuộc tính này là true, tôi muốn đặt thuộc IsEnabledtính trên Nút, (ví dụ), thành false.

Tôi muốn tin rằng tôi có thể làm điều đó dễ dàng như thế IsEnabled="{Binding Path=!IsReadOnly}"nhưng điều đó không xảy ra với WPF.

Tôi có xuống hạng khi phải trải qua tất cả các cài đặt kiểu không? Có vẻ như quá dài dòng cho một cái gì đó đơn giản như đặt một bool thành nghịch đảo của một bool khác.

<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 làm điều tốt nhưng không hoàn thành nó
user1005462

Câu trả lời:


488

Bạn có thể sử dụng ValueConverter để đảo ngược thuộc tính bool cho bạn.

XAML:

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

Chuyển đổi:

[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
Có một vài điều tôi phải xem xét ở đây, điều đó có thể sẽ khiến tôi chọn câu trả lời của @ Paul về vấn đề này. Tôi đang ở một mình khi viết mã (bây giờ), vì vậy tôi cần phải đi với một giải pháp mà "tôi" sẽ nhớ, mà tôi sẽ sử dụng nhiều lần. Tôi cũng cảm thấy rằng càng ít thứ gì đó càng tốt thì việc tạo ra một thuộc tính nghịch đảo là rất rõ ràng, giúp tôi dễ nhớ, cũng như các nhà phát triển trong tương lai (I Hope, I Hope), để có thể nhanh chóng nhìn thấy những gì tôi đang làm, cũng như giúp họ dễ dàng ném tôi xuống xe buýt hoạt ngôn.
Nga

17
Theo lập luận của riêng bạn, IMHO giải pháp chuyển đổi sẽ tốt hơn trong dài hạn: bạn chỉ phải viết trình chuyển đổi một lần và sau đó bạn có thể sử dụng lại nhiều lần. Nếu bạn đi tìm tài sản mới, bạn sẽ phải viết lại nó trong mọi lớp học cần nó ...
Thomas Levesque

51
Tôi đang sử dụng cùng một cách tiếp cận ... nhưng nó làm cho gấu trúc saaad ... = (
Max Galkin

27
So với !, đó là một số mã dài dòng ... Mọi người nỗ lực hết sức để tách những gì họ cảm thấy là "mã" khỏi những nhà thiết kế nghèo nàn đó. Thêm cực kỳ đau đớn khi tôi vừa là lập trình viên vừa là nhà thiết kế.
Roman Starkov

10
nhiều người bao gồm cả bản thân tôi sẽ coi đây là một ví dụ điển hình của kỹ thuật quá mức. Tôi đề nghị sử dụng một tài sản đảo ngược như trong bài viết của Paul Alexander dưới đây.
Christian Westman

99

Bạn đã xem xét một IsNotReadOnlytài sản? Nếu đối tượng bị ràng buộc là ViewModel trong miền MVVM, thì thuộc tính bổ sung có ý nghĩa hoàn hảo. Nếu đó là mô hình Thực thể trực tiếp, bạn có thể xem xét thành phần và trình bày một ViewModel chuyên dụng của thực thể của mình cho biểu mẫu.


5
Tôi chỉ giải quyết vấn đề tương tự bằng cách sử dụng phương pháp này và tôi đồng ý rằng nó không chỉ thanh lịch hơn mà còn dễ bảo trì hơn nhiều so với sử dụng Bộ chuyển đổi.
alimbada

28
Tôi không đồng ý rằng phương pháp này tốt hơn công cụ chuyển đổi giá trị. Nó cũng tạo ra nhiều mã hơn nếu bạn cần một số phiên bản NotProperty.
Thiru

25
MVVM không phải là không viết mã, mà là giải quyết vấn đề khai báo. Cuối cùng, bộ chuyển đổi giải pháp chính xác.
Jeff

14
Vấn đề với giải pháp này là nếu bạn có 100 đối tượng, bạn sẽ phải thêm một thuộc tính IsNotReadOnly cho tất cả 100 đối tượng. Tài sản đó sẽ phải là một DependencyProperty. Điều đó thêm khoảng 10 dòng mã cho tất cả 100 đối tượng hoặc 1000 dòng mã. Bộ chuyển đổi là 20 dòng mã. 1000 dòng hoặc 20 dòng. bạn chọn cái nào?
Rhyous

8
Có một câu nói phổ biến cho việc này: làm một lần, làm hai lần và sau đó tự động hóa. Nghi ngờ, tôi sẽ sử dụng câu trả lời này ngay lần đầu tiên cần thiết trong một dự án và sau đó nếu mọi thứ phát triển, tôi sẽ sử dụng câu trả lời được chấp nhận. Nhưng việc có đoạn mã chuyển đổi được tạo sẵn có thể làm cho nó ít khó sử dụng hơn.
heltonbiker

71

Với ràng buộc độc lập, bạn cần sử dụng các bộ chuyển đổi trông có vẻ ít gió. Vì vậy, tôi khuyên bạn nên xem dự án CalcBinding của tôi, dự án được phát triển đặc biệt để giải quyết vấn đề này và một số vấn đề khác. Với liên kết nâng cao, bạn có thể viết biểu thức với nhiều thuộc tính nguồn trực tiếp trong xaml. Nói, bạn có thể viết một cái gì đó như:

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

hoặc là

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

hoặc là

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

hoặc là

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

Trong đó A, B, C, IsChecked - thuộc tính của viewModel và nó sẽ hoạt động đúng


6
Mặc dù QuickConverter mạnh hơn nhưng tôi thấy chế độ CalcBinding có thể đọc được - có thể sử dụng được.
xmedeko

3
Đây là một công cụ tuyệt vời. Tôi ước nó tồn tại 5 năm trước!
jugg1es

Công cụ rực rỡ, nhưng rơi vào phong cách. <Setter.Value><cb:Binding Path="!IsReadOnly" /></Setter.Value>nhận được 'Binding' không hợp lệ đối với Setter.Value 'lỗi thời gian biên dịch
mcalex

21

Tôi khuyên bạn nên sử dụng https://quickconverter.codeplex.com/

Đảo ngược một boolean sau đó đơn giản như: <Button IsEnabled="{qc:Binding '!$P', P={Binding IsReadOnly}}" />

Điều đó tăng tốc thời gian thường cần để viết bộ chuyển đổi.


19
Khi đưa -1 cho ai đó, thật tuyệt khi giải thích lý do.
Noxxys

16

Tôi muốn XAML của mình vẫn thanh lịch nhất có thể vì vậy tôi đã tạo một lớp để bọc bool nằm trong một trong các thư viện dùng chung của mình, các toán tử ẩn cho phép lớp được sử dụng như một bool trong mã phía sau một cách liền mạch

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

}

Những thay đổi duy nhất cần thiết cho dự án của bạn là làm cho thuộc tính bạn muốn đảo ngược trả lại cái này thay vì bool

    public InvertableBool IsActive 
    { 
        get 
        { 
            return true; 
        } 
    }

Và trong hậu tố XAML, liên kết với Giá trị hoặc Đảo ngược

IsEnabled="{Binding IsActive.Value}"

IsEnabled="{Binding IsActive.Invert}"

1
Nhược điểm là bạn phải thay đổi tất cả các mã so sánh nó với / gán nó cho các boolBiểu thức / Biến loại khác thậm chí không tham chiếu giá trị nghịch đảo. Thay vào đó tôi sẽ thêm Phương thức mở rộng "Không" vào Boolean Struct.
Tom

1
Đừng! Đừng bận tâm. Quên phải là Propertyvs.Method với Binding. Tuyên bố "Nhược điểm" của tôi vẫn được áp dụng. Btw, Phương thức mở rộng 'Boolean` "Không" vẫn hữu ích để tránh "!" Toán tử dễ bị bỏ qua khi nó (như thường lệ) được nhúng bên cạnh các ký tự trông giống như nó (tức là một / nhiều "(" và "l" và "I").
Tom

10

Điều này cũng làm việc cho các bool 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
}

4

Thêm một thuộc tính nữa trong mô hình xem của bạn, sẽ trả về giá trị ngược. Và liên kết nó với nút. Giống;

trong mô hình xem:

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

trong xaml:

IsEnabled="{Binding IsNotReadOnly"}

1
Câu trả lời chính xác. Thêm một điều nữa, bằng cách sử dụng này, bạn nên tăng sự kiện PropertyChanged cho IsNotReadOnly trong trình thiết lập cho thuộc tính IsReadOnly. Với điều này, bạn sẽ đảm bảo UI được cập nhật chính xác.
Muhannad

Đây phải là câu trả lời được chấp nhận vì nó là đơn giản nhất.
gabnaim

2

Không biết điều này có liên quan đến XAML không, nhưng trong ứng dụng Windows đơn giản của tôi, tôi đã tạo liên kết theo cách thủ công và thêm trình xử lý sự kiện 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;
}

1
Có vẻ khá giống với phương pháp chuyển đổi. FormatParsecác sự kiện trong các ràng buộc WinForms tương đương với trình chuyển đổi WPF.
Alejandro

2

Tôi đã có một vấn đề đảo ngược, nhưng một giải pháp gọn gàng.

Động lực là nhà thiết kế XAML sẽ hiển thị một điều khiển trống, ví dụ như khi không có datacontext / no MyValues(mục nguồn).

Mã ban đầu: ẩn điều khiển khi MyValuestrống. Mã cải tiến: hiển thị kiểm soát khi MyValuesKHÔNG rỗng hoặc trống.

Vấn đề là làm thế nào để thể hiện '1 hoặc nhiều mục', ngược lại với 0 mục.

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

Tôi đã giải quyết nó bằng cách thêm:

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

Ergo thiết lập mặc định cho các ràng buộc. Tất nhiên, điều này không hoạt động đối với tất cả các loại vấn đề nghịch đảo, nhưng đã giúp tôi giải quyết được mã sạch.


2

💡 Lõi Solution Net 💡

Xử lý tình huống null và không ném ngoại lệ, nhưng trả về truenếu không có giá trị nào được trình bày; mặt khác lấy Boolean đã nhập và đảo ngược nó.

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 Tôi muốn đặt tất cả các thống kê chuyển đổi của mình vào tệp app.xaml để tôi không phải phân phối lại chúng trong các cửa sổ / trang / điều khiển của dự án.

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

Để rõ ràng converters:là không gian tên để thực hiện lớp thực tế ( xmlns:converters="clr-namespace:ProvingGround.Converters").


1

Theo câu trả lời của @ Paul, tôi đã viết như sau trong ViewModel:

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

Tôi hy vọng có một đoạn trích ở đây sẽ giúp được ai đó, có lẽ là người mới như tôi.
Và nếu có lỗi, xin vui lòng cho tôi biết!

BTW, tôi cũng đồng ý với nhận xét của @heltonbiker - đó chắc chắn là cách tiếp cận chính xác chỉ khi bạn không phải sử dụng nó hơn 3 lần ...


2
Không phải là một tài sản đầy đủ và thiếu một "OnPropertyChanged", điều này sẽ không hoạt động. Câu trả lời thứ 1 hoặc thứ 2 là những gì tôi đang sử dụng, tùy theo trường hợp. Trừ khi bạn đang sử dụng một khung như Prism nơi frameowkr biết khi nào cần cập nhật các thuộc tính "được giới thiệu". Sau đó, đó là một sự thay đổi giữa việc sử dụng một cái gì đó giống như những gì bạn đề xuất (nhưng có đầy đủ tài sản) và trả lời 1
Oyiwai

1

Tôi đã làm một cái gì đó rất giống nhau. Tôi đã tạo tài sản của mình phía sau hậu trường cho phép lựa chọn một hộp tổ hợp CHỈ nếu nó đã hoàn thành việc tìm kiếm dữ liệu. Khi cửa sổ của tôi xuất hiện lần đầu tiên, nó sẽ khởi chạy lệnh được tải không đồng bộ nhưng tôi không muốn người dùng nhấp vào hộp tổ hợp trong khi nó vẫn đang tải dữ liệu (sẽ trống, sau đó sẽ được điền). Vì vậy, theo mặc định, thuộc tính là sai vì vậy tôi trả về nghịch đảo trong getter. Sau đó, khi tôi đang tìm kiếm, tôi đặt thuộc tính thành true và trở về false khi hoàn thành.

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

Sau đó, đối với combobox tôi có thể liên kết trực tiếp với IsSearching:

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

0

Tôi sử dụng một cách tiếp cận tương tự như @Ofaim

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

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

public bool EnableSaveButton => !jobSaved;
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.