วิธีการผูกคุณสมบัติบูลีนผกผันใน 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.