WPF-dagi tugmani qanday yoqish/o'chirish mumkin?

Menda oddiy wpf dasturi bor. Idora foydalanuvchi ob'ektlari ro'yxatini taqdim etish; har bir element uchun elementni tanlash/tanlash uchun belgilash katakchasi mavjud.

Mening kodim, soddalashtirilgan, biroz o'xshash:

 class Thing { /* ... */ };
 public static int SelectedCount { get; set; }
 public class SelectableThing
 {
        private bool _selected;
        public bool Selected { 
            get { return _selected; }
            set { _selected = value; if (value) { SelectedCount++; } else { SelectedCount--; } } 
        }
        public Thing thing { get; set; }
 };
 private ObservableCollection _selectableThings;
 public Collection { get { return _selectableThings; } }
 
      
      
 
 <button Content="{Binding Path=SelectedTestCount}" Click="someFunc" />

Shu sababli, tugma tarkibida tanlangan testlar soni ko'rsatilishi kerak. Buni amalga oshirish kerak, chunki SelectableThing.Selected tanlanganda, kerakli darajada SelectedCount ni oshirishi/kamaytirishi kerak.

Biroq, xatti-harakatlarning ishi yo'qligini ayta olaman. Tugma matni ro'yxatdagi elementlarni tanlash yoki tanlashdan qat'i nazar, "0" ni ko'rsatadi.

Har qanday fikr bormi?

3
Viewmodels dan foydalanmoqdaman.
qo'shib qo'ydi muallif Stephen Gross, manba
Viewmodellardan foydalanasizmi yoki kodni orqada qoldirasizmi?
qo'shib qo'ydi muallif Yatrix, manba

2 javoblar

Sizda bir nechta moda sinflari mavjud bo'lgani uchun bu muammo biroz tuklar. Buni hal qilish uchun kodda yorliq. Yo'qotilgan yagona narsa - siz satrni chiqarmaguncha DataGrid ma'lumotlaringizni yangilash kabi ko'rinmaydi.

At start, before any edits. The save button is disabled, and the count is zero After some edits. The save button is enabled, and the count is updated

XAML:


    
        
            
            
        
        
            
                
                
            
        
        <button Content="{Binding Path=SelectedTestCount}"
                Command="{Binding SaveCommand}"
                Grid.Row="1" Width="75" Height="23"
                HorizontalAlignment="Right" Margin="0,0,6,6"/>
    

Sinf sinf:

public class Thing : INotifyPropertyChanged
{
    private readonly List selectableThings;
    private DelegateCommand saveCommand;

    public Thing(IEnumerable selectableThings)
    {
        this.selectableThings = new List(selectableThings);
        this.SelectableThings =
            new ObservableCollection(this.selectableThings);

        this.SaveCommand = this.saveCommand = new DelegateCommand(
            o => Save(),
            o => SelectableThings.Any(t => t.IsSelected)
            );

       //Bind children to change event
        foreach (var selectableThing in this.selectableThings)
        {
            selectableThing.PropertyChanged += SelectableThingChanged;
        }

        SelectableThings.CollectionChanged += SelectableThingsChanged;
    }

    public ObservableCollection SelectableThings
    {
        get;
        private set;
    }

    public int SelectedTestCount
    {
        get { return SelectableThings.Where(t => t.IsSelected).Count(); }
    }

    public ICommand SaveCommand { get; private set; }

    private void Save()
    {
       //Todo: Implement
    }

    private void SelectableThingChanged(object sender,
        PropertyChangedEventArgs args)
    {
        if (args.PropertyName == "IsSelected")
        {
            RaisePropertyChanged("SelectedTestCount");
            saveCommand.RaiseCanExecuteChanged();
        }
    }

    private void SelectableThingsChanged(object sender,
        NotifyCollectionChangedEventArgs e)
    {
        foreach (SelectableThing selectableThing in
            e.OldItems ?? new List())
        {
            selectableThing.PropertyChanged -= SelectableThingChanged;
            RaisePropertyChanged("SelectedTestCount");
        }

        foreach (SelectableThing selectableThing in
            e.NewItems ?? new List())
        {
            selectableThing.PropertyChanged += SelectableThingChanged;
            RaisePropertyChanged("SelectedTestCount");
        }
    }

    public void RaisePropertyChanged(string propertyName)
    {
        if(PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

SelectableSinf sinf:

public class SelectableThing : INotifyPropertyChanged
{
    private string name;
    private bool isSelected;

    public SelectableThing(string name)
    {
        this.name = name;
    }

    public string Name
    {
        get { return name; }
        set
        {
            name = value;
            RaisePropertyChanged("Name");
        }
    }

    public bool IsSelected
    {
        get { return isSelected; }
        set
        {
            isSelected = value;
            RaisePropertyChanged("IsSelected");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void RaisePropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Original javob:

Command kodini ICommand ga ulang. Sizning shartingiz qoniqtirilmasa FALSE kodini qaytarish uchun ICommand saytida CanExecute ni o'rnating.

Ushbu IsSelected xususiyati uchun sozlagichda, qiymat o'zgarganda, CanExecuteChanged hodisasini ko'taring.

Agar Button da Buyruq ga ulanish, CanExecute natijasi asosida tugmani avtomatik ravishda o'chirib qo'yadi/o'chiradi.

Buni qanday amalga oshirish kerakligi haqida batafsil ma'lumotni, shu jumladan bu erda foydalanishingiz mumkin ICommand ilovasini ko'rish uchun bu mini-MVVM o'quvchisi Boshqa savolga yozib qo'ydim.

CanExecute ilovasini to'ldirish uchun men Linqning .bir usuli kabi bir narsani ishlataman. Keyin Count kodini tekshirishni to'xtatishga majbur emassiz va biron bir elementni tekshirganligini bilsangiz, dastlab erta tugmani bekor qilishingiz mumkin.

Misol uchun:

this.SaveCommand = new DelegateCommand(Save, CanSave);

// ...

private void Save(object unusedArg)
{
   //Todo: Implement
}

private bool CanSave(object unusedArg)
{
    return SelectableThings.Any(t => t.IsSelected);
}

Yoki qisqa bo'lgani uchun lambda satrini kiriting:

this.SaveCommand = new DelegateCommand(Save,
    o => SelectableThings.Any(t => t.IsSelected)
    );
3
qo'shib qo'ydi
IsSelected ga asoslangan CanExecuteChanged kodini o'zgartirish haqida bit narsa juda qiyin bo'lishi mumkin, chunki siz bolalarga qarashli modellar to'plamidan foydalanmoqdasiz. Har birida INotifyPropertyChanged ga obuna bo'lishingiz va hodisani this.SaveCommand.RaiseCanExecuteChanged() ga yuborishingiz mumkin. Bu qismni tashvishga solayotgan bo'lsangiz, menga xabar bering.
qo'shib qo'ydi muallif Merlyn Morgan-Graham, manba
Bundan tashqari, siz IsSelected xususiyatini nomini tavsiya qilaman. Boolean qiymatlari (Is, Jon, Has, va hokazo) nomlashda ushbu konvensiyani kuzatishingiz kerak.
qo'shib qo'ydi muallif Merlyn Morgan-Graham, manba
@StephenGross: Bir statik a'zoligidan foydalanishda ishlashi mumkin bo`lsa, u o`zgarishdir. Sizning mulkingiz (" Thing ?) Bu xususiyatni ushlab turadigan bir nechta misolni qo'llab-quvvatlamaysiz. Ushbu kod uchun birlik testlarini yozish ham qiyin bo'ladi. Bundan tashqari, wpf statik xususiyatlarga bog'langanmi yoki yo'qmi, yoki INotifyPropertyChanged ishlayotganida amin emasman.
qo'shib qo'ydi muallif Merlyn Morgan-Graham, manba
@Stephen: Men nimani nazarda tutayotganimni ko'rsatish uchun bir necha kod qo'shdim, chunki bu muammoni yomon narsa. Men sizning echimingiz siz uchun INotifyPropertyChanged narsalaridan bir qismini saralaydigan asboblar to'plamidan foydalanmasdan toza bir ma'lumotga ega bo'lasiz.
qo'shib qo'ydi muallif Merlyn Morgan-Graham, manba
@Stephen: Muammo shundaki, har bir tasdiqlash qutisi o'zgarganda siz matnni/matn qutisini yangilashingiz kerak. Buning yagona yo'li - barcha tadbirlarga obuna bo'lish. wpf hisob-varag'i voqeasini ro'yxatga oladi, ammo jami tadbirlarga obuna bo'lishning oddiy usuli yo'q. Buni hal qilish uchun tasdiqlash qutilariga tetiğe ishlov berishi mumkin, lekin uni ishlash uchun qabul qilish uchun ba'zi bir orqa so'nggi konvertor kodiga kerak bo'lishi mumkin (bu javobda xususan Linqga o'xshash ilovalar bilan).
qo'shib qo'ydi muallif Merlyn Morgan-Graham, manba
@Stephen: Men taklif qilgandan ko'ra boshqacha usul bilan ishlashni boshlasangiz, uni alohida javob sifatida kiritishingizni va uni qabul qilishingizni tavsiya qilaman.
qo'shib qo'ydi muallif Merlyn Morgan-Graham, manba
Kirishingizni aks ettirish uchun asl savolni o'zgartirdim; Menga o'zingizning fikringizni bildirishga ruxsat bera olasizmi?
qo'shib qo'ydi muallif Stephen Gross, manba
Ha, statik intdan foydalanganimning yagona sababi, uni SelectableThing.Selected.set() dan o'zgartira olmaysiz.
qo'shib qo'ydi muallif Stephen Gross, manba
OK, siz o'zingizning taklif qilingan tavsiyangizni ko'rib chiqyapman. Ko'rinib turibdiki, oddiy uslub bo'lishi kerak ... Islinsiz boolean o'zgarishlar yuzaga kelganda, o'rnatilgan "int SelectedCount" xususiyati bo'lishi mumkin emasmi? Agar shunday bo'lsa, men tekshiruvlarni SelectedCount-ga bog'lash va ularni shu tarzda diskretlash imkoniyatiga ega bo'lishim kerak.
qo'shib qo'ydi muallif Stephen Gross, manba

Tugmaning mazmunini viewmodel-dagi maydonga bog'lang va har bir element tanlangan yoki tanlanmagan har safar ushbu maydon uchun OnChanged usulini yoqing. IsEnabledni o'zingizning modelingizdagi boolean maydonga ulang va tugmachani yoqish yoki o'chirish uchun uni to'g'ri/haqiqiy deb o'zgartiring.

1
qo'shib qo'ydi
Buni sinash uchun asl savolni o'zgartirdim, lekin ehtimol noto'g'ri (!) ...
qo'shib qo'ydi muallif Stephen Gross, manba