วิธีการผูกคุณสมบัติบูลีนผกผันใน WPF?


378

สิ่งที่ฉันมีคือวัตถุที่มีIsReadOnlyคุณสมบัติ หากคุณสมบัตินี้เป็นจริงฉันต้องการตั้งค่าIsEnabledคุณสมบัติเป็นปุ่ม (ตัวอย่าง) เป็นเท็จ

ฉันอยากจะเชื่อว่าฉันสามารถทำมันได้อย่างง่ายดายเหมือนIsEnabled="{Binding Path=!IsReadOnly}"แต่มันไม่ได้บินกับ WPF

ฉันได้ลดการตั้งค่าสไตล์ทั้งหมดหรือไม่? ดูเหมือนจะใช้คำพูดเกินไปสำหรับบางสิ่งที่ง่ายพอ ๆ กับการตั้งค่าบูลหนึ่งให้เป็นอินเวอร์สของบูลอื่น

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

ที่เกี่ยวข้อง: stackoverflow.com/questions/534575/…
— UuDdLrLrSs

เอ๊ะ MS ทำสิ่งที่ดี แต่ไม่สมบูรณ์
— user1005462

คำตอบ:


488

คุณสามารถใช้ ValueConverter ที่กลับคุณสมบัติบูลสำหรับคุณ

XAML:

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

Converter:

[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
มีบางสิ่งที่ฉันต้องพิจารณาที่นี่ซึ่งอาจจะทำให้ฉันเลือก @ Paul คำตอบมากกว่านี้ ฉันอยู่คนเดียวเมื่อโค้ด (ตอนนี้) ดังนั้นฉันต้องไปด้วยโซลูชันที่ "ฉัน" จะจำซึ่งฉันจะใช้ซ้ำแล้วซ้ำอีก ฉันยังรู้สึกว่าบางสิ่งที่ใช้คำฟุ่มเฟือยน้อยกว่านั้นดีกว่าและการสร้างคุณสมบัติผกผันนั้นชัดเจนมากทำให้ฉันจำได้ง่ายและอุปกรณ์ในอนาคต (ฉันหวังว่าฉันหวังว่า) เพื่อให้สามารถเห็นสิ่งที่ฉันได้อย่างรวดเร็ว กำลังทำอยู่รวมทั้งทำให้พวกเขาโยนฉันลงใต้รถประจำทางสุภาษิตได้ง่ายขึ้น
— Russ

17
ด้วยเหตุผลของคุณเอง IMHO โซลูชันตัวแปลงจะดีกว่าในระยะยาว: คุณต้องเขียนตัวแปลงเพียงครั้งเดียวและหลังจากนั้นคุณสามารถนำมาใช้ซ้ำได้ หากคุณไปสำหรับคุณสมบัติใหม่ที่คุณจะต้องเขียนไว้ในระดับที่ต้องการมัน ... ทุก
— โทมัส Levesque

51
ฉันใช้วิธีการเดียวกัน ... แต่มันทำให้แพนด้า saaad ... = (
— Max Galkin

27
เปรียบเทียบกับ!นั่นคือรหัสที่ยืดยาว ... ผู้คนพยายามอย่างบ้าคลั่งเพื่อแยกสิ่งที่พวกเขารู้สึกว่าเป็น "รหัส" จากนักออกแบบที่น่าสงสาร เจ็บปวดเป็นพิเศษเป็นพิเศษเมื่อฉันทั้งรหัสและผู้ออกแบบ
— Roman Starkov

10
คนจำนวนมากรวมถึงตัวฉันเองจะถือว่านี่เป็นตัวอย่างสำคัญของการทำวิศวกรรมมากเกินไป ฉันแนะนำให้ใช้คุณสมบัติคว่ำเช่นเดียวกับใน Paul Alexander ที่โพสต์ด้านล่าง
— Christian Westman

99

คุณเคยพิจารณาIsNotReadOnlyทรัพย์สินหรือไม่? หากวัตถุที่ถูกผูกไว้เป็น ViewModel ในโดเมน MVVM แล้วคุณสมบัติเพิ่มเติมทำให้รู้สึกที่สมบูรณ์แบบ หากเป็นโมเดลเอนทิตีโดยตรงคุณอาจพิจารณาองค์ประกอบและนำเสนอ ViewModel แบบพิเศษของเอนทิตีของคุณไปยังแบบฟอร์ม


5
ฉันเพิ่งแก้ไขปัญหาเดียวกันโดยใช้วิธีการนี้และฉันยอมรับว่าไม่เพียง แต่จะสวยงามกว่า แต่ยังบำรุงรักษาได้ดีกว่าการใช้ตัวแปลง
— alimbada

28
ฉันไม่เห็นด้วยว่าวิธีนี้ดีกว่าตัวแปลงค่า นอกจากนี้ยังสร้างรหัสเพิ่มเติมหากคุณต้องการอินสแตนซ์ NotProperty หลายรายการ
— Thiru

25
MVVM ไม่ได้เกี่ยวกับการไม่เขียนโค้ด แต่เป็นการแก้ปัญหาอย่างชัดเจน ด้วยเหตุนี้ตัวแปลงเป็นทางออกที่ถูกต้อง
— Jeff

14
ปัญหาเกี่ยวกับวิธีแก้ไขปัญหานี้คือถ้าคุณมีวัตถุ 100 รายการคุณจะต้องเพิ่มคุณสมบัติ IsNotReadOnly ให้กับวัตถุทั้งหมด 100 รายการ คุณสมบัตินั้นจะต้องเป็น DependencyProperty ซึ่งเพิ่มโค้ดประมาณ 10 บรรทัดให้กับวัตถุ 100 รายการหรือ 1,000 บรรทัดของรหัส ตัวแปลงเป็นโค้ด 20 บรรทัด 1,000 บรรทัดหรือ 20 บรรทัด คุณจะเลือกแบบไหน
— Rhyous

8
มีคำพูดทั่วไปสำหรับเรื่องนี้: ทำครั้งเดียวทำสองครั้งแล้วทำโดยอัตโนมัติ ด้วยความสงสัยฉันจะใช้คำตอบนี้ในครั้งแรกที่จำเป็นในโครงการแล้วถ้าสิ่งต่าง ๆ เติบโตขึ้นฉันจะใช้คำตอบที่ยอมรับได้ แต่การมีข้อมูลโค้ดที่สร้างไว้ล่วงหน้าอาจทำให้ยากต่อการใช้งาน
— heltonbiker

71

ด้วยการเชื่อมโยงมาตรฐานคุณจำเป็นต้องใช้ตัวแปลงที่ดูมีลมแรงเล็กน้อย ดังนั้นฉันขอแนะนำให้คุณดูที่โครงการCalcBindingของฉันซึ่งพัฒนาขึ้นเป็นพิเศษเพื่อแก้ไขปัญหานี้และอื่น ๆ ด้วยการโยงขั้นสูงคุณสามารถเขียนนิพจน์ที่มีคุณสมบัติแหล่งที่มามากมายใน xaml โดยตรง พูดว่าคุณสามารถเขียนสิ่งที่ชอบ:

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

หรือ

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

หรือ

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

หรือ

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

โดยที่ A, B, C, IsChecked - คุณสมบัติของ viewModel และมันจะทำงานอย่างถูกต้อง


6
แม้ว่า QuickConverter จะมีประสิทธิภาพมากกว่าฉันพบว่าโหมด CalcBinding สามารถอ่านได้ - ใช้งานได้
— xmedeko

3
นี่เป็นเครื่องมือที่ยอดเยี่ยม ฉันหวังว่ามันจะมีอยู่ 5 ปีที่ผ่านมา!
— jugg1es

เครื่องมือยอดเยี่ยม แต่มีสไตล์มากกว่า <Setter.Value><cb:Binding Path="!IsReadOnly" /></Setter.Value>ได้รับ 'เข้าเล่ม' ไม่ถูกต้องสำหรับข้อผิดพลาดเวลารวบรวม
— Setter.Value

21

ฉันจะแนะนำให้ใช้ https://quickconverter.codeplex.com/

อินเวอร์เตอร์บูลีนนั้นง่ายเหมือน: <Button IsEnabled="{qc:Binding '!$P', P={Binding IsReadOnly}}" />

ที่เพิ่มความเร็วเวลาที่ปกติต้องเขียนตัวแปลง


19
เมื่อให้ -1 กับใครสักคนมันจะดีที่จะอธิบายว่าทำไม
— Noxxys

16

ฉันต้องการให้ XAML ของฉันยังคงสง่างามที่สุดเท่าที่จะเป็นไปได้ดังนั้นฉันจึงสร้างคลาสเพื่อห่อบูลที่อยู่ในไลบรารีที่แชร์ของฉันตัวดำเนินการโดยนัยอนุญาตให้ใช้คลาสเป็นบูลในโค้ด - ต่อเนื่อง

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

}

การเปลี่ยนแปลงเพียงอย่างเดียวที่จำเป็นสำหรับโครงการของคุณคือการทำให้คุณสมบัติที่คุณต้องการกลับคืนแทนที่จะเป็นบูล

    public InvertableBool IsActive 
    { 
        get 
        { 
            return true; 
        } 
    }

และใน XAML postfix การรวมกับ Value หรือ Invert

IsEnabled="{Binding IsActive.Value}"

IsEnabled="{Binding IsActive.Invert}"

1
ข้อเสียคือคุณต้องเปลี่ยนรหัสทั้งหมดที่เปรียบเทียบกับ / กำหนดให้กับboolประเภท / ตัวแปรอื่น ๆแม้ว่าจะไม่ได้อ้างอิงค่าผกผันก็ตาม ฉันแทนจะเพิ่มเป็น "ไม่" Boolean Structขยายวิธีไป
— Tom

1
Doh! ไม่เป็นไร. ลืมจะต้องPropertyเทียบกับสำหรับMethod Bindingคำสั่ง "Downside" ของฉันยังคงมีผลอยู่ Btw, 'บูลีน' "ไม่ใช่" วิธีการขยายยังคงมีประโยชน์สำหรับการหลีกเลี่ยง "!" โอเปอเรเตอร์ที่พลาดได้ง่ายเมื่อมัน (ตามที่มักจะเป็น) จะถูกฝังไว้ข้างตัวอักษรที่ดูเหมือน (เช่นหนึ่ง / มากกว่า "(" 's และ' l '' s และ "I")
— Tom

10

อันนี้ใช้ได้กับบูลที่ไม่มีค่าอีกด้วย

 [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

เพิ่มคุณสมบัติอีกหนึ่งรายการในโมเดลมุมมองของคุณซึ่งจะส่งคืนค่าย้อนกลับ และผูกปุ่มนั้นไว้ ชอบ;

ในรูปแบบการดู:

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

ใน xaml:

IsEnabled="{Binding IsNotReadOnly"}

1
คำตอบที่ดี สิ่งหนึ่งที่จะเพิ่มโดยใช้สิ่งนี้คุณควรเพิ่มเหตุการณ์ PropertyChanged สำหรับ IsNotReadOnly ใน setter สำหรับคุณสมบัติ IsReadOnly ด้วยวิธีนี้คุณจะต้องแน่ใจว่า UI ได้รับการอัปเดตอย่างถูกต้อง
— Muhannad

นี่ควรเป็นคำตอบที่ยอมรับได้เพราะมันง่ายที่สุด
— gabnaim

2

ไม่ทราบว่าสิ่งนี้เกี่ยวข้องกับ XAML หรือไม่ แต่ในแอพ Windows แบบง่ายของฉันฉันสร้างการเชื่อมโยงด้วยตนเองและเพิ่มตัวจัดการเหตุการณ์รูปแบบ

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
ดูเหมือนจะสวยกว่าวิธีการแปลง FormatและParseเหตุการณ์ในการผูก WinForms นั้นเทียบเท่ากับตัวแปลง WPF โดยประมาณ
— Alejandro

2

ฉันมีปัญหากลับกัน แต่เป็นวิธีแก้ไขที่เรียบร้อย

แรงจูงใจคือการที่นักออกแบบ XAML จะแสดงการควบคุมที่ว่างเปล่าเช่นเมื่อไม่มี datacontext / no MyValues(itemssource)

รหัสเริ่มต้น: ซ่อนการควบคุมเมื่อMyValuesไม่มีข้อมูล ปรับปรุงรหัส: แสดงการควบคุมเมื่อMyValuesไม่ว่างเปล่าหรือว่างเปล่า

Ofcourse ปัญหาคือวิธีแสดง '1 รายการขึ้นไป' ซึ่งตรงข้ามกับ 0 รายการ

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

ฉันแก้ไขได้โดยการเพิ่ม:

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

Ergo การตั้งค่าเริ่มต้นสำหรับการผูก แน่นอนว่านี่ใช้ไม่ได้กับปัญหาผกผันทุกประเภท แต่ช่วยฉันด้วยโค้ดที่สะอาด


2

💡 .Net Core Solution 💡

จัดการกับสถานการณ์ที่เป็น null และไม่ส่งข้อยกเว้น แต่ส่งคืนtrueหากไม่มีการแสดงค่า มิฉะนั้นจะใช้บูลีนอินพุตและกลับรายการ

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ฉันชอบที่จะใส่ statics คอนเวอร์เตอร์ของฉันทั้งหมดลงในไฟล์ app.xaml ดังนั้นฉันไม่ต้องประกาศใหม่ในหน้าต่าง / เพจ / การควบคุมของโครงการ

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

จะชัดเจน converters:เป็น namespace เพื่อการใช้งานจริงระดับ ( xmlns:converters="clr-namespace:ProvingGround.Converters")


1

ทำตามคำตอบของ @ Paul ฉันได้เขียนสิ่งต่อไปนี้ใน ViewModel:

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

ฉันหวังว่าการมีตัวอย่างบางส่วนที่นี่จะช่วยใครซักคนซึ่งอาจเป็นมือใหม่ในขณะที่ฉัน
และหากมีข้อผิดพลาดโปรดแจ้งให้เราทราบ!

BTW ฉันยังเห็นด้วยกับความคิดเห็น @heltonbiker - มันเป็นวิธีที่ถูกต้องเฉพาะในกรณีที่คุณไม่ต้องใช้มากกว่า 3 ครั้ง ...


2
ไม่ได้เป็นคุณสมบัติแบบเต็มและขาด "OnPropertyChanged" สิ่งนี้จะไม่ทำงาน คำตอบที่หนึ่งหรือที่สองคือสิ่งที่ฉันใช้ขึ้นอยู่กับกรณี นอกเสียจากว่าคุณกำลังใช้เฟรมเวิร์กอย่างปริซึมซึ่ง frameowkr รู้ว่าเมื่อใดควรอัปเดตคุณสมบัติ "อ้างอิง" จากนั้นก็เป็นการโยนระหว่างการใช้สิ่งที่คุณแนะนำ (แต่เต็มไปด้วยคุณสมบัติ) และตอบ 1
— Oyiwai

1

ฉันทำสิ่งที่คล้ายกันมาก ฉันสร้างสถานที่ให้บริการของฉันที่อยู่เบื้องหลังซึ่งเปิดใช้งานการเลือก combobox เท่านั้นหากการค้นหาข้อมูลเสร็จสิ้น เมื่อหน้าต่างของฉันปรากฏขึ้นเป็นครั้งแรกมันจะเปิดใช้งานคำสั่งโหลด async แต่ฉันไม่ต้องการให้ผู้ใช้คลิกที่ combobox ในขณะที่มันยังคงโหลดข้อมูล (จะว่างเปล่าจากนั้นจะมีประชากร) ดังนั้นโดยปกติแล้วคุณสมบัติเป็นเท็จดังนั้นฉันจะคืนค่าผกผันในทะเยอทะยาน จากนั้นเมื่อฉันค้นหาฉันตั้งค่าคุณสมบัติเป็นจริงและกลับสู่เท็จเมื่อเสร็จสมบูรณ์

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

จากนั้นสำหรับ combobox ฉันสามารถผูกมันโดยตรงกับการค้นหา:

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

0

ฉันใช้วิธีที่คล้ายกันเช่น @Ofaim

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

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

public bool EnableSaveButton => !jobSaved;
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.