I want to bind visibility of a textblock, within a listbox, to a bool value in my ViewModel. Binding works well to a textblock outside the listbox, but it isn't working to the textblock within the listbox. Please help!
xaml code:
<TextBlock x:Name="heading" Visibility="{Binding MyVb.Visible, Converter={StaticResource BoolToVisConverter}}" Width="480"/>
<ListBox x:Name="lstBani1" ItemsSource="{Binding Users}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock x:Name="tb1" Text="{Binding string1}" Visibility="{Binding MyVb.Visible, Converter={StaticResource BoolToVisConverter}}" Width="480"/>
<TextBlock x:Name="tb2" Text="{Binding string2}" Width="480"/>
<TextBlock x:Name="tb3" Text="{Binding string3}" Width="480"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
cs code:
public partial class Page1 : PhoneApplicationPage
{
public Page1()
{
ViewModel model = new ViewModel();
model.Users = GetUsers();
model.MyVb = new MyVisibility();
model.MyVb.Visible = false;
this.DataContext = model;
}
// View Model
public class ViewModel
{
public List<User> Users { get; set; }
public MyVisibility MyVb { get; set; }
}
// Bool to Visibility Converter
public class BooleanToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool)value ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
// Property Classes
public class User
{
public string string1 { get; set; }
public string string2 { get; set; }
public string string3 { get; set; }
}
public class MyVisibility : INotifyPropertyChanged
{
private bool _Visible;
public event PropertyChangedEventHandler PropertyChanged;
public bool Visible
{
get { return _Visible; }
set
{
_Visible = value;
NotifyPropertyChanged("Visible");
}
}
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
private List<Bani> GetUsers() {....}
}
Your ListBox is bound to Users, therefore each item in the list is has a DataContext bound to a User. Therefore your binding is trying to look for the property in the User class, not the parent data context.
Give your page a Name and change your binding to the following:
Visibility="{Binding DataContext.MyVb.Visible, ElementName=yourPageName, Converter={StaticResource BoolToVisConverter}}"
Related
In my WPF app, I want to use an ObservableCollection to contain some Wave classes. Each Wave has an ObservableCollection to contain some Couple class. It looks like:
ObservableCollection<Wave> Waves { get; set; }
- int StartYear { get; set; }
- ObservableCollection<Couple> Couples { get; set; }
- int A { get; set; }
- int B { get; set; }
- some other Properties
After Waves have been added, int properties work well. However, the Couples in every Wave changes when each of them has been changed. They have the same GUID.
How can Coupless in every Wave are different?
Here is my code:
// Couple.cs
public class Couple : DependencyObject
{
public int A { get { return (int)GetValue(AProperty); } set { SetValue(AProperty, value); } }
public int B { get { return (int)GetValue(BProperty); } set { SetValue(BProperty, value); } }
public static readonly DependencyProperty AProperty = DependencyProperty.Register("A", typeof(int), typeof(Couple), new PropertyMetadata(0));
public static readonly DependencyProperty BProperty = DependencyProperty.Register("B", typeof(int), typeof(Couple), new PropertyMetadata(0));
}
// Wave.cs
class Wave : DependencyObject
{
public ObservableCollection<Couple> Couples
{
get { return (ObservableCollection<Couple>)GetValue(CouplesProperty); }
set { SetValue(CouplesProperty, value); }
}
public static readonly DependencyProperty CouplesProperty = DependencyProperty.Register("Couples", typeof(ObservableCollection<Couple>), typeof(Wave), new PropertyMetadata(new ObservableCollection<Couple>()));
}
// XAML of the UserControl
<ItemsControl Grid.Column="1" ItemsSource="{Binding Waves}" ScrollViewer.HorizontalScrollBarVisibility="Visible" Margin="0,0,0,6" Focusable="False" ScrollViewer.CanContentScroll="False" VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Width="240">
<ItemsControl ItemsSource="{Binding Couples}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40"/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="01" TextAlignment="Center"/>
<TextBox Grid.Column="1" Text="{Binding A}"/>
<TextBox Grid.Column="2" Text="{Binding B}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I am new to MVVM. Perviously, the values are got by use Items property of ListBox, but now I want to change these code to adapt to MVVM mode. Thank you!
The problem here is that you set a non-null default value for a dependency property that is of a mutable reference type. All instances of the class that owns the property (i.e. all Wave objects) will use the same default collection object.
The constructor of the Wave class should set an initial value like
class Wave
{
public static readonly DependencyProperty CouplesProperty =
DependencyProperty.Register(
nameof(Couples),
typeof(ObservableCollection<Couple>),
typeof(Wave));
public Wave()
{
Couples = new ObservableCollection<Couple>();
}
...
}
Besides that, you should not derive your view model classes from DependencyObject. They should instead implement the INotifyPropertyChanged interface if necessary:
public class Couple : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int a;
public int A
{
get { return a; }
set
{
a = value;
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(A)));
}
}
private int b;
public int B
{
get { return b; }
set
{
b = value;
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(B)));
}
}
}
class Wave
{
public ObservableCollection<Couple> Couples { get; }
= new ObservableCollection<Couple>();
}
Note that the Couples property is now read-only, and the owning class does hence not need to fire a change notification for that property.
For the INotifyPropertyChanged implementation you would typically have a base class that encapsulates the invokation of the PropertyChanged event, like
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected void SetValue<T>(ref T field, T value, string propertyName)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
NotifyPropertyChanged(propertyName);
}
}
}
and use it like
public class Couple : ViewModelBase
{
private int a;
public int A
{
get { return a; }
set { SetValue(ref a, value, nameof(A)); }
}
private int b;
public int B
{
get { return b; }
set { SetValue(ref b, value, nameof(B)); }
}
}
I want to bind a list of buttons in combo boxes. Each Combobox will contains buttons of one category and so on. As referred in attached image.
Below is my code:
<ItemsControl x:Name="iNumbersList">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel HorizontalAlignment="Center" VerticalAlignment="Center" Orientation="Horizontal" MaxWidth="930"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Click="ItemButtonClick"
Tag="{Binding ItemTag}"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Height="100" Width="300">
<TextBlock TextAlignment="Center" Foreground="Red"
HorizontalAlignment="Center" FontWeight="SemiBold"
FontSize="25" TextWrapping="Wrap"
Text="{Binding ItemDisplayMember}"/>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
public class NumberModel
{
public string ItemDisplayMember { get; set; }
public object ItemTag { get; set; }
public string ItemCategory { get; set; }
}
How can I group by ItemCategory property and bind it on GUI, a ComboBox for each ItemCategory and then multiple buttons in it?
Probably you don't need a ComboBox but Expander because the objective is reachable using it. ComboBox is needed when you have to filter something inside it or use dropdown displayed above Windows content.
I wrote a simple example using MVVM programming pattern. There will be many new classes but most of it you need add to the project only once. Let's go from the scratch!
1) Create class NotifyPropertyChanged to implement INotifyPropertyChanged interface. It needed to make able Binding to update the layout dynamically in Runtime.
NotifyPropertyChanged.cs
public class NotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
2) Create MainViewModel class derived from NotifyPropertyChanged. It will be used for Binding target Properties.
MainViewModel.cs
public class MainViewModel : NotifyPropertyChanged
{
public MainViewModel()
{
}
}
3) Attach MainViewModel to MainWindow's DataContext. One of ways - doing it in xaml.
MainWindow.xaml
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
4) Derive the data class NumberModel from NotifyPropertyChanged and add OnPropertyChanged call for each Property. This will give wonderful effect: when you change any Property in runtime, You'll immediately see the changes in UI. MVVM Magic called Binding :)
NumberModel.cs
public class NumberModel : NotifyPropertyChanged
{
private string _itemDisplayMember;
private object _itemTag;
private string _itemCategory;
public string ItemDisplayMember
{
get => _itemDisplayMember;
set
{
_itemDisplayMember = value;
OnPropertyChanged();
}
}
public object ItemTag
{
get => _itemTag;
set
{
_itemTag = value;
OnPropertyChanged();
}
}
public string ItemCategory
{
get => _itemCategory;
set
{
_itemCategory = value;
OnPropertyChanged();
}
}
}
5) When button is clicked I will not handle the Click event but call a Command. For easy use of Commands I suggest this relaying its logic class (grabbed here).
RelayCommand.cs
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Func<object, bool> _canExecute;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter);
public void Execute(object parameter) => _execute(parameter);
}
6) All ready to fill the MainViewModel with code. I've added there a command and some items to collection for test.
MainViewModel.cs
public class MainViewModel : NotifyPropertyChanged
{
private ObservableCollection<NumberModel> _itemsList;
private ICommand _myCommand;
public ObservableCollection<NumberModel> ItemsList
{
get => _itemsList;
set
{
_itemsList = value;
OnPropertyChanged();
}
}
public ICommand MyCommand => _myCommand ?? (_myCommand = new RelayCommand(parameter =>
{
if (parameter is NumberModel number)
MessageBox.Show("ItemDisplayMember: " + number.ItemDisplayMember + "\r\nItemTag: " + number.ItemTag.ToString() + "\r\nItemCategory: " + number.ItemCategory);
}));
public MainViewModel()
{
ItemsList = new ObservableCollection<NumberModel>
{
new NumberModel { ItemDisplayMember = "Button1", ItemTag="Tag1", ItemCategory = "Category1" },
new NumberModel { ItemDisplayMember = "Button2", ItemTag="Tag2", ItemCategory = "Category1" },
new NumberModel { ItemDisplayMember = "Button3", ItemTag="Tag3", ItemCategory = "Category1" },
new NumberModel { ItemDisplayMember = "Button4", ItemTag="Tag4", ItemCategory = "Category2" },
new NumberModel { ItemDisplayMember = "Button5", ItemTag="Tag5", ItemCategory = "Category2" },
new NumberModel { ItemDisplayMember = "Button6", ItemTag="Tag6", ItemCategory = "Category2" },
new NumberModel { ItemDisplayMember = "Button7", ItemTag="Tag7", ItemCategory = "Category3" },
new NumberModel { ItemDisplayMember = "Button8", ItemTag="Tag8", ItemCategory = "Category4" },
new NumberModel { ItemDisplayMember = "Button9", ItemTag="Tag9", ItemCategory = "Category4" }
};
}
}
7) The main answer to your main question is: use IValueConverter to filter the list with requred criteria. I wrote 2 converters. First for Categories, second for Buttons.
Converters.cs
public class CategoryConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is ObservableCollection<NumberModel> collection)
{
List<NumberModel> result = new List<NumberModel>();
foreach (NumberModel item in collection)
{
if (!result.Any(x => x.ItemCategory == item.ItemCategory))
result.Add(item);
}
return result;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class ItemGroupConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values[0] is ObservableCollection<NumberModel> collection && values[1] is string categoryName)
{
List<NumberModel> result = new List<NumberModel>();
foreach (NumberModel item in collection)
{
if (item.ItemCategory == categoryName)
result.Add(item);
}
return result;
}
return null;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
8) Now everything is ready to fill the markup. I post full markup here to make everything clear.
Note: I faced Visual Studio 2019 16.5.4 crash while setting a MultiBinding in ItemsSource and applied the workaround.
MainWindow.xaml
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp1"
Title="MainWindow" Height="600" Width="1000" WindowStartupLocation="CenterScreen">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<local:CategoryConverter x:Key="CategoryConverter"/>
<local:ItemGroupConverter x:Key="ItemGroupConverter"/>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding ItemsList, Converter={StaticResource CategoryConverter}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:NumberModel}">
<Expander Header="{Binding ItemCategory}">
<ItemsControl DataContext="{Binding DataContext,RelativeSource={RelativeSource AncestorType=Window}}">
<ItemsControl.Style>
<Style TargetType="ItemsControl">
<Setter Property="ItemsSource">
<Setter.Value>
<MultiBinding Converter="{StaticResource ItemGroupConverter}">
<Binding Path="ItemsList"/>
<Binding Path="Header" RelativeSource="{RelativeSource AncestorType=Expander}"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.Style>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel HorizontalAlignment="Left" VerticalAlignment="Center" Orientation="Horizontal" MaxWidth="930"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:NumberModel}">
<Button Tag="{Binding ItemTag}"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Height="100" Width="300"
Command="{Binding DataContext.MyCommand,RelativeSource={RelativeSource AncestorType=Window}}"
CommandParameter="{Binding}">
<TextBlock TextAlignment="Center" Foreground="Red"
HorizontalAlignment="Center" FontWeight="SemiBold"
FontSize="25" TextWrapping="Wrap"
Text="{Binding ItemDisplayMember}"/>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Expander>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
Job is done with MVVM. Enjoy. :)
P.S. Ah, yes, I forgot to show you the code-behind class. Here it is!
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
I have a ListView that binds its items from an ObservableCollection, and a Button that changes an "Amount" property of a specific object of that ObservableCollection. And I want to change the BackgroundColor of these Items whose "Amount" has already been changed.
I've searched for a solution for that, but I couldn't find any.
Does anybody know a way for solving that?
One way to do it would be to add a new property, something like HasAmountChanged, bind the background color of the viewcell to that property, and use a ValueConverter to set the color. This would look something like the following:
The object class with the properties:
public class MyObject : INotifyPropertyChanged
{
double amount;
bool hasAmountChanged = false;
public event PropertyChangedEventHandler PropertyChanged;
public MyObject(double amount)
{
this.amount = amount;
}
public double Amount
{
get => amount;
set
{
if (amount != value)
{
amount = value;
OnPropertyChanged(nameof(Amount));
HasAmountChanged = true;
}
}
}
public bool HasAmountChanged
{
get => hasAmountChanged;
set
{
if (hasAmountChanged != value)
{
hasAmountChanged = value;
OnPropertyChanged(nameof(HasAmountChanged));
}
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The view. Notice the stacklayout inside the ViewCell, that's where the background color is set:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Delete"
x:Class="Delete.MainPage">
<ContentPage.Resources>
<ResourceDictionary>
<local:ListViewBackgroundColorConverter x:Key="ListViewColorConverter" />
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout>
<Button Text="Click Me" Clicked="ButtonClicked" />
<ListView ItemsSource="{Binding MyItemsSource}" HasUnevenRows="True">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Spacing="15"
BackgroundColor="{Binding HasAmountChanged, Converter={StaticResource ListViewColorConverter}}"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand">
<Label Text="FOO 1"/>
<Label Text="{Binding Amount}"/>
<Label Text="{Binding HasAmountChanged}" />
<Label Text="FOO 4"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
The code behind of the view, included for completeness:
public partial class MainPage : ContentPage
{
public ObservableCollection<MyObject> MyItemsSource { get; set; }
public MainPage()
{
InitializeComponent();
MyItemsSource = new ObservableCollection<MyObject>
{
new MyObject(1.14),
new MyObject(1.14),
new MyObject(1.14),
new MyObject(1.14),
new MyObject(1.14),
};
BindingContext = this;
}
void ButtonClicked(object sender, EventArgs e)
{
var rnd = new Random();
var myObject = MyItemsSource[rnd.Next(0, MyItemsSource.Count)];
myObject.Amount = 5.09;
}
}
And finally the most important part, the converter:
public class ListViewBackgroundColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool)value ? Color.LawnGreen : Color.DarkRed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Note that you would actually want to check it's a bool coming in and handle that as well.
You could implement an array of booleans and change them to true when Amount gets changed. Then you might want to create a custom renderer for the color of each ListView.
So let's say I have a simple little WPF app that looks like this:
-------------------------------
Amount Left: 1,000
[Subtract 1]
[Subtract 5]
[Subtract 15]
[Subtract 30]
-------------------------------
"Amount Left:" and "1,000" are stand alone TextBlocks.
The "Subtract x" are all buttons, inside a ListView, inside a DataTemplate. Each time a button is clicked, the amount of the button is subtracted from the 1,000. All of that I have working.
Here's what I can't figure out. When the Amount Left falls below 30, the last button needs to become disabled. When the amount falls below 15, the second to last button becomes disabled. Etc and so on, until the Amount Left is Zero and all buttons are disabled. I can not figure out how to disable the buttons.
This example I'm giving here is not exactly what I'm trying to do, but it's a greatly simplified example that will make this post a lot shorter and simpler. Here, in essence, is what I have now.
XAML:
<DockPanel>
<TextBlock Text="Amount Left:" />
<TextBlock x:Name="AmountLeft" Text="1,000.00" />
</DockPanel>
<DockPanel>
<ListBox x:Name="AuthorListBox">
<ListView.ItemTemplate>
<DataTemplate>
<Button x:Name="SubButtom" Content="{Binding SubtractAmount}" Click="clickSubtract" />
<DataTemplate>
</ListBox>
</DockPanel>
XAML.cs
private void clickSubtract(object sender, RoutedEventArgs e)
{
Button button = sender as Button;
Int32 SubtractAmount = ((Data.AppInformation)button.DataContext).SubtractAmount; // This is the amount to be subtracted
// logic to update the amount remaining. This works.
// What I need to figure out is how to disable the buttons
}
You can accomplish using MVVM, by having an IsEnabled property for your Button ViewModels. With this approach, you will not need any 'code behind' as you currently have using a click event handler.
Xaml:
<Grid>
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Amount Left:" />
<TextBlock Text="{Binding CurrentAmount}" />
</StackPanel>
<ListBox ItemsSource="{Binding Buttons}">
<ListBox.ItemTemplate>
<DataTemplate>
<Button Command="{Binding SubtractCommand}" Width="200" Height="75" x:Name="SubButtom" Content="{Binding SubtractAmount}" IsEnabled="{Binding IsEnabled}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Grid>
Xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
We want the main ViewModel that will have a list of Button ViewModels.
ButtonViewModel.cs:
namespace WpfApplication1
{
public class ButtonViewModel : INotifyPropertyChanged
{
private bool _isEnabled;
private ViewModel _viewModel;
public bool IsEnabled
{
get { return _isEnabled; }
set
{
_isEnabled = value;
OnPropertyChanged();
}
}
public int SubtractAmount { get; set; }
public ICommand SubtractCommand { get; private set; }
public ButtonViewModel(ViewModel viewModel)
{
_viewModel = viewModel;
IsEnabled = true;
SubtractCommand = new CommandHandler(() =>
{
_viewModel.CurrentAmount -= SubtractAmount;
}, true);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class CommandHandler : ICommand
{
private readonly Action _action;
private readonly bool _canExecute;
public CommandHandler(Action action, bool canExecute)
{
_action = action;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_action();
}
}
}
and now the main ViewModel.
ViewModel.cs:
namespace WpfApplication1
{
public class ViewModel : INotifyPropertyChanged
{
private int _currentAmount;
public int CurrentAmount
{
get { return _currentAmount; }
set
{
_currentAmount = value;
OnPropertyChanged();
if (Buttons != null)
{
foreach (var button in Buttons)
{
if ((value - button.SubtractAmount) <= 0)
{
button.IsEnabled = false;
}
}
}
}
}
public List<ButtonViewModel> Buttons { get; private set; }
public ViewModel()
{
CurrentAmount = 1000;
Buttons = new List<ButtonViewModel>
{
new ButtonViewModel(this)
{
SubtractAmount = 1
},
new ButtonViewModel(this)
{
SubtractAmount = 5
},
new ButtonViewModel(this)
{
SubtractAmount = 15
},
new ButtonViewModel(this)
{
SubtractAmount = 30
}
};
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
As you can see, each Button ViewModel will decrement the CurrentAmount using a Command (the preferred method over a click event). Whenever the CurrentAmount is changed, some simple logic is done by the main ViewModel that will disable associated buttons.
This is tested and works. Let me know if you have any questions.
I would go ahead creating a Converter and bind the IsEnabled property of the button. pass the value and do the logic.
Namespace
System.Windows.Data
System.Globalization
CODE
public class IsEnabledConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
// Do the logic
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
// Do the Logic
}
}
XAML
Add the resurce like this
<Window.Resources>
<local:IsEnabledConverter x:Key="converter" />
</Window.Resources>
<Button x:Name="SubButtom" IsEnabled="{Binding Value, Converter= {StaticResource converter}}" Content="{Binding SubtractAmount}" Click="clickSubtract" />
You can learn about converters from below link
http://wpftutorial.net/ValueConverters.html
When you build the class with Converter all Xaml Errors will go off.
Your best option would be to use a command on the viewmodel instead of a click event handler:
public ICommand SubtractCommand = new DelegateCommand<int>(Subtract, i => i <= AmountLeft);
private void Subtract(int amount)
{
AmountLeft = AmountLeft - amount;
}
XAML:
<ListBox x:Name="AuthorListBox">
<ListBox.ItemTemplate>
<DataTemplate>
<Button x:Name="SubButtom" Content="{Binding SubtractAmount}"
Command="{Binding SubtractCommand, RelativeSource={RelativeSource AncestorType=DataGrid}}"
CommandParameter="{Binding SubtractAmount}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In my ViewModel i Have base Card class and Deck class which contain Observable Collection of Cards. Here is how it is bound in XAML
<GridView ItemsSource="{Binding DeckCollection}" IsItemClickEnabled="True" Grid.Row="0">
<GridView.ItemTemplate>
<DataTemplate>
<Button Command="{Binding Path=??}"
CommandParameter=??
<Button.Content>
<Grid>
<Image
Source="{Binding ImagePath}"
Stretch="None"/>
</Grid>
</Button.Content>
</Button>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
Here are my classes
class Deck
{
private ObservableCollection<Card> _deckCollection = new ObservableCollection<Card>();
public ObservableCollection<Card> DeckCollection
{
get { return _deckCollection; }
set { _deckCollection = value; }
}
public Deck()
{
ActionCommand = new MyCommand();
ActionCommand.CanExecuteFunc = obj => true;
ActionCommand.ExecuteFunc = AddToList;
}
public void AddToList(object parameter)
{
var clickedCard = this;
//add Card to list which in this case is not possible
//DeckCollection.Add(this) ?
}
}
class Card
{
public String Name { get; set; }
public int Cost { get; set; }
public String ImagePath { get; set; }
public MyCommand ActionCommand { get; set; }
}
And also MyCommand class
public class MyCommand : ICommand
{
public Predicate<object> CanExecuteFunc { get; set; }
public Action<object> ExecuteFunc { get; set; }
public bool CanExecute(object parameter)
{
return CanExecuteFunc(parameter);
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
ExecuteFunc(parameter);
}
}
I have made suggested changes but right now ActionCommand is not visible within collection, as only properties that belong to Card class can be bound.
EDIT:I have changed my XAML file for following but got some errors
<Button Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Deck}, Path=ActionCommand}}">
The property 'AncestorType' was not found in type 'RelativeSource'.
The property 'Path' was not found in type 'RelativeSource'.
The member "AncestorType" is not recognized or is not accessible.
The member "Path" is not recognized or is not accessible.
Unknown member 'AncestorType' on element 'RelativeSource'
Unknown member 'Path' on element 'RelativeSource'
Please help
If you want to have button which adds new items to your collection, I think something like that can be the solution.
In XAML:
<GridView ItemsSource="{Binding DeckCollection}" IsItemClickEnabled="True" Grid.Row="0">
<GridView.ItemTemplate>
<DataTemplate>
<Button>
<Button.Content>
<Grid>
<Image Source="{Binding ImagePath}"
Stretch="None"/>
</Grid>
</Button.Content>
</Button>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
<!-- public property located in Deck class -->
<Button Command="{Binding AddItemCommand}" Content="Add Item"/>
In C#:
class Deck, INotifyPropertyChanged /*custom implementation depends on .NET version, in my case its .NET3.5*/
{
private ObservableCollection<Card> _deckCollection = new ObservableCollection<Card>();
public ObservableCollection<Card> DeckCollection
{
get { return _deckCollection; }
set { _deckCollection = value;
OnPropertyChanged(() => DeckCollection); }
}
// your Add command
public ICommand AddItemCommand { get { return new MyCommand(AddToList); } }
private void AddToList(object parameter)
{
DeckCollection.Add(new Card());
}
public Deck() { }
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged<T>(Expression<Func<T>> expression)
{
if (PropertyChanged == null) return;
var body = (MemberExpression)expression.Body;
if (body != null) PropertyChanged.Invoke(this, new PropertyChangedEventArgs(body.Member.Name));
}
}
The main thing in this situation is that you cannot have the add button inside the collection.