In WPF, I have a ListView of 2 columns and the first column needs to be a button. Correct me if I'm wrong, but the only way I found to implement a button in a ListView is to use a DataTemplate. The problem I found with this is I have no way to maintain my original button Properties when they are mapped with a DataTemplate so I am forced to use binding to remap every individual property (including custom Properties since I'm actually using a custom User Control which inherits from Button). This seems extraneous to have to manually map all Properties so maybe there's a better way to automatically persist those properties?
Here's my test code:
public MainWindow() {
InitializeComponent();
ObservableCollection<ScreenRequest> screenRequests = new ObservableCollection<ScreenRequest>() {
new ScreenRequest("A", "1"),
new ScreenRequest("B", "2")
};
myListView.ItemsSource = screenRequests;
}
public class ScreenRequest {
public CustomButton ScreenButton { set; get; }
public string Details { set; get; }
public ScreenRequest(string buttonText, string customProperty) {
this.ScreenButton = new CustomButton();
this.ScreenButton.Content = buttonText;
this.ScreenButton.CustomProperty = customProperty;
this.ScreenButton.Click += new RoutedEventHandler(InitiateScreenRequest);
}
private void InitiateScreenRequest(object sender, RoutedEventArgs e) {
CustomButton screenBtn = (CustomButton)sender;
screenBtn.Content = "BUTTON TEXT CHANGED";
}
}
public class CustomButton : Button {
public string CustomProperty { get; set; }
}
And the XAML:
<Window...
...
<Window.Resources>
<DataTemplate x:Key="ButtonTemplate">
<local:CustomButton Content="{Binding ScreenButton.Content}"/>
</DataTemplate>
</Window.Resources>
<Grid x:Name="grdMain">
...
<ListView...
<ListView.View>
<GridView x:Name="gridView">
<GridViewColumn CellTemplate="{StaticResource ButtonTemplate}" Width="Auto" Header="Screen" HeaderStringFormat="Screen"/>
<GridViewColumn Header="Details" HeaderStringFormat="Details" DisplayMemberBinding="{Binding Details}"/>
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
So my questions are:
Do I have to manually map every single property in the CustomButton in order for it to carry over to the DataTemplate or is their a catch-all to automatically persist the Properties?
How do I map the CustomProperty Property in the binding such that it sticks with the button? Do I use a DependencyProperty for this?
How do I maintain my click event such that clicking the button in GridView will call the InitiateScreenRequest function? Ideally I'd like to have a single method declared for all buttons, but I haven't gotten to that point yet.
Any help or insight into buttons in listviews would be appreciated.
<Window x:Class="MiscSamples.TonyRush"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TonyRush" Height="300" Width="300">
<ListView ItemsSource="{Binding}">
<ListView.View>
<GridView>
<GridViewColumn Width="Auto" Header="Screen" HeaderStringFormat="Screen">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button Command="{Binding SomeAction}" Content="{Binding ActionDescription}" Width="100"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Details" HeaderStringFormat="Details" DisplayMemberBinding="{Binding Details}" Width="100"/>
</GridView>
</ListView.View>
</ListView>
</Window>
Code Behind:
public partial class TonyRush : Window
{
public TonyRush()
{
InitializeComponent();
DataContext = new List<ScreenRequest>
{
new ScreenRequest() {ActionDescription = "Click Me!"},
new ScreenRequest() {ActionDescription = "Click Me Too!"},
new ScreenRequest() {ActionDescription = "Click Me Again!!"},
};
}
}
ViewModel:
public class ScreenRequest: INotifyPropertyChanged
{
public Command SomeAction { get; set; }
private string _actionDescription;
public string ActionDescription
{
get { return _actionDescription; }
set
{
_actionDescription = value;
NotifyPropertyChanged("ActionDescription");
}
}
private string _details;
public string Details
{
get { return _details; }
set
{
_details = value;
NotifyPropertyChanged("Details");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public ScreenRequest()
{
SomeAction = new Command(ExecuteSomeAction) {IsEnabled = true};
}
//public SomeProperty YourProperty { get; set; }
private void ExecuteSomeAction()
{
//Place your custom logic here based on YourProperty
ActionDescription = "Clicked!!";
Details = "Some Details";
}
}
Key part: The Command class:
//Dead-simple implementation of ICommand
//Serves as an abstraction of Actions performed by the user via interaction with the UI (for instance, Button Click)
public class Command : ICommand
{
public Action Action { get; set; }
public void Execute(object parameter)
{
if (Action != null)
Action();
}
public bool CanExecute(object parameter)
{
return IsEnabled;
}
private bool _isEnabled;
public bool IsEnabled
{
get { return _isEnabled; }
set
{
_isEnabled = value;
if (CanExecuteChanged != null)
CanExecuteChanged(this, EventArgs.Empty);
}
}
public event EventHandler CanExecuteChanged;
public Command(Action action)
{
Action = action;
}
}
Result:
Notes:
Take a look at how separate UI is from Data and functionality. This is the WPF way. Never mix UI with data / business code.
The Command in the ViewModel serves as an abstraction for the Button. The ViewModel doesn't know what a Button is, nor should it. Let me know if you need further details.
Related
I have ObservableCollection of DeviceInformation which is added in MainWindowViewModel and linked with DataContext.
public partial class MainWindow : Window
{
MainWindowViewModel viewModel = new MainWindowViewModel();
public MainWindow()
{
InitializeComponent();
this.DataContext = viewModel;
}
}
Here is the MainWindowViewModel:
public class MainWindowViewModel : ViewModelBase
{
private ObservableCollection<DeviceInformation> allDeviceInfo = new ObservableCollection<DeviceInformation>();
public MainWindowViewModel()
{
// here some of the commands
}
public ObservableCollection<DeviceInformation> AllDeviceInfo
{
get { return allDeviceInfo; }
set
{
allDeviceInfo = value;
this.RaisePropertyChanged(nameof(AllDeviceInfo));
}
}
}
The RaisePropertyChanged is done with implementing ViewModelBase which looks like this:
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
this.RaisePropertyChanged(new PropertyChangedEventArgs(propertyName));
}
protected virtual void RaisePropertyChanged(PropertyChangedEventArgs e)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, e);
}
}
}
Inside my DeviceInformation I have a List of SyntaxMessages:
public class DeviceInformation : ViewModelBase
{
private List<SyntaxMessages> list = new List<SyntaxMessages>();
private string test = "";
public List<SyntaxMessages> ComConsoleMessages{
get { return list; } // get method
set
{
list = value;
RaisePropertyChanged(nameof(ComConsoleMessages));
} // set method
}
public string Test{
get { return test; } // get method
set
{
test = value;
RaisePropertyChanged(nameof(Test));
} // set method
}
}
This is how the SyntaxMessages looks:
public class SyntaxMessages : ViewModelBase
{
#region private values
private string message = "";
private string status = "";
private string color = "Transparent";
#endregion
#region Public values
public string Message {
get { return message; }
set
{
message = value;
RaisePropertyChanged(nameof(Message));
}
}
public string Status {
get { return status; }
set
{
status = value;
RaisePropertyChanged(nameof(Status));
}
}
public string Color {
get { return color; }
set
{
color = value;
RaisePropertyChanged(nameof(Color));
}
}
#endregion
}
So when I running my program and connecting device to it will collect and add information the the DeviceInformation and this will be added to ObervableCollection of DeviceInformation. This will update my MainTabControl by adding new tab and binding many strings like "Test" (there is more then one) to the TextBoxes, and also update the SubTabControl which is inside the main one. Inside both of the TabItems inside SubTabControl I also have a ListView to which I want link the List of SyntaxMessages this looks like this:
<ListView
Grid.Row="4"
Grid.Column="0"
Grid.ColumnSpan="5"
MinHeight="40"
Padding="0"
Margin="2"
ItemsSource="{Binding ComConsoleMessages, UpdateSourceTrigger=PropertyChanged}">
<ListView.View>
<GridView
AllowsColumnReorder="False">
<GridViewColumn
DisplayMemberBinding="{Binding Message}"
Header="Message" />
<GridViewColumn
Header="Status">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Status}" Foreground="{Binding Color}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
Problem
All works fine except a ListView. When I add some SyntaxMessages to the List of SyntaxMessages called ComConsoleMessages then I have to switch between SubTabControl tabs(SubTabItem1/2) to see ListView updated. I want to update ListView every single time when new message is added to the List of SyntaxMessages which is inside DeviceInfromation which is indside ObservableCollection of DeviceInfromations that is linked via MainWindowViewMode to the window DataContext.
Here is the view:
In my view, I have a ListBox with some templated items that contain buttons.
<ListBox x:Name="MyListBox" ItemTemplate="{DynamicResource DataTemplate1}"
ItemsSource="{Binding MyItems}">
</ListBox>
And the template for generated items:
<DataTemplate x:Key="DataTemplate1">
<StackPanel Orientation="Horizontal">
<Button Width="50" Click="Button_Click" />
</StackPanel>
</DataTemplate>
When user clicks a button on one of those ListBox items, I want to send the index of that ListBox item to my ViewModel.
So figured to use Binding as it seems to be the way in MVVM. But I'm struggling to set up a binding in code between two properties.
My View code is as follows:
public partial class ItemView : UserControl
{
ViewModel.ItemViewModel VM;
public ItemView()
{
InitializeComponent();
VM = new ViewModel.ItemViewModel();
this.DataContext = VM;
}
private int clickedItemIndex;
public int ClickedItemIndex { get => clickedItemIndex; set => clickedItemIndex = value; }
private void Button_Click(object sender, RoutedEventArgs e)
{
var ClickedItem = (sender as FrameworkElement).DataContext;
ClickedItemIndex = MyListBox.Items.IndexOf(ClickedItem);
}
}
I get the index and set it to ClickedItemIndex property,
I also have property in my ViewModel:
public int SomeInt { get; set; }
Now how do I set up a binding between these two properties?
I'm quite new to MVVM and still learning it. So, maybe this not the correct approach. But I need to have a way for each individual listbox item to be able to call upon an effect in more global viewmodel. For example, if I wanted to have a "Remove" button on each of the listbox items, I would somehow need to send the index to the viewmodel and call the removeItem method with index as the parameter. Or is there a better way to do similar things?
I have a sample app created just for this scenario. I know it seems a lot of code at first glance. Copy this code in your project, that will help debug and get a hang of it(MVVM, databinding, commands and so on).
usercontrol.xaml
<UserControl x:Class="WpfApplication1.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type local:Model}">
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Path=Name}"/>
<Button Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=DataContext.UpdateCommand}"
CommandParameter="{Binding}"
Content="Update"/>
<Button Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=DataContext.RemoveCommand}"
CommandParameter="{Binding}"
Content="Remove"/>
</StackPanel>
</DataTemplate>
</Grid.Resources>
<ListBox ItemsSource="{Binding Models}">
</ListBox>
</Grid>
usercontrol.cs
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
View model
public class ViewModel : INotifyPropertyChanged
{
private Models _Models;
public Models Models
{
get { return _Models; }
set { _Models = value;
PropertyChanged(this, new PropertyChangedEventArgs(nameof(Models)));
}
}
public ViewModel()
{
Models = new Models();
UpdateCommand = new Command(o => true, UpdateItem);
RemoveCommand = new Command(o => true, RemoveItem);
}
void RemoveItem(object item)
{
Model m = (item as Model);
Models.Remove(m);
}
void UpdateItem(object item)
{
Model m = (item as Model);
m.Name = m.Name + " updated";
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public ICommand UpdateCommand { get; private set; }
public ICommand RemoveCommand { get; private set; }
}
Icommand implementation
public class Command : ICommand
{
private readonly Func<object, bool> _canExe;
private readonly Action<object> _exe;
public Command(Func<object,bool> canExecute,Action<object> execute)
{
_canExe = canExecute;
_exe = execute;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return _canExe(parameter);
}
public void Execute(object parameter)
{
_exe(parameter);
}
}
Model and a collection of models
public class Models : ObservableCollection<Model>
{
public Models()
{
Add(new Model ());
Add(new Model ());
Add(new Model ());
Add(new Model ());
}
}
public class Model : INotifyPropertyChanged
{
static int count = 0;
public Model()
{
Name = "Model "+ ++count;
}
private string _Name;
public string Name
{
get { return _Name; }
set { _Name = value;
PropertyChanged(this, new PropertyChangedEventArgs(nameof(Name)));
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
You don't need to use a Button in order to select the item. When you click/tap on the item it will get automatically selected.
Then simply bind ListBox.SelectedIndex to your view model property SomeInt and it will update on every selection.
Data binding overview in WPF
You can also get the item itself by binding ListBox.SelectedItem to your view model.
You can handle new values by invoking a handler from the property's set method:
ViewModel.cs
class ViewModel : INotifyPropertyChanged
{
private int currentItemIndex;
public int CurrentItemIndex
{
get => this.currentItemIndex;
set
{
this.currentItemIndex = value;
OnPropertyChanged();
// Handle property changes
OnCurrentItemIndexChanged();
}
}
private MyItem currentItem;
public MyItem CurrentItem
{
get => this.currentItem;
set
{
this.currentItem = value;
OnPropertyChanged();
}
}
protected virtual void OnCurrentItemIndexChanged()
{
// Handle the new this.CurrentItemIndex value
}
// Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
ItemView .xaml
<UserControl>
<UserControl.DataContext>
<ViewModel />
</UserControl.DataContext>
<ListBox ItemsSource="{Binding MyItems}"
SelectedIndex="{Binding CurrentItemIndex}"
SelectedItem="{Binding CurrentItem}" />
</UserControl>
I am developing a UWP application and I need to show data in RadDataGrid control of Telerik. In one scenario I need to show data using TemplatedColumn and bind commands to controls placed inside its DataTemplate but command are not getting triggered in ViewModel but when I attach event to these controls events get triggered in code behind.
Here is the code:
<Interactivity:Interaction.Behaviors>
<Core:EventTriggerBehavior EventName="Loaded">
<Core:CallMethodAction MethodName="LoadData"
TargetObject="{Binding}" />
</Core:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>
<Grid x:Name="gdRoot">
<telerikGrid:RadDataGrid ItemsSource="{x:Bind AvailableVM.PickListItems,Mode=OneWay}"
Background="{StaticResource GridLinesBrush}"
SelectionUnit="Cell"
GridLinesBrush="{StaticResource GridLinesBrush}"
AlternateRowBackground="{StaticResource AlternateRowBackground}"
AutoGenerateColumns="False"
ScrollViewer.VerticalScrollBarVisibility="Hidden">
<telerikGrid:RadDataGrid.Columns>
<telerikGrid:DataGridTemplateColumn Header="Assign"
SizeMode="Auto">
<telerikGrid:DataGridTemplateColumn.CellContentTemplate>
<DataTemplate>
<Button Background="Transparent"
Command="{Binding DataContext.ListSelectedCommand, ElementName=gdRoot}"/>
</DataTemplate>
</telerikGrid:DataGridTemplateColumn.CellContentTemplate>
</telerikGrid:DataGridTemplateColumn>
</telerikGrid:RadDataGrid.Columns>
</telerikGrid:RadDataGrid>
</Grid>
here is the ViewModel Code:
private ICommand _listSelectedCommand;
public ICommand ListSelectedCommand
{
get { return _listSelectedCommand; }
set { Set(nameof(ListSelectedCommand), ref _listSelectedCommand,value); }
}
public void LoadData()
{
InitializeCommands();
}
private void InitializeCommands()
{
ListSelectedCommand= new RelayCommand(()=>
{
});
}
What could be the possible reason behind this.
The most possible reason for the command does't work should be you didn't bind the command correctly. Since your code snippet is not the full, the incorrect binding may be caused by many reasons. Here is a small demo I tested which can work on my side you may reference.
XAML:
<telerikGrid:RadDataGrid ItemsSource="{x:Bind AvailableVM.PickListItems,Mode=OneWay}"
Background="White"
SelectionUnit="Cell"
GridLinesBrush="Pink"
AlternateRowBackground="Azure"
AutoGenerateColumns="False"
ScrollViewer.VerticalScrollBarVisibility="Hidden"
x:Name="radgrid">
<telerikGrid:RadDataGrid.Columns>
<telerikGrid:DataGridTextColumn PropertyName="Country"/>
<telerikGrid:DataGridTextColumn PropertyName="City"/>
<telerikGrid:DataGridTemplateColumn Header="Assign" SizeMode="Auto">
<telerikGrid:DataGridTemplateColumn.CellContentTemplate>
<DataTemplate x:DataType="local:DataTest">
<Button Background="Transparent" Command="{x:Bind ListSelectedCommand }" Content="command testing" />
</DataTemplate>
</telerikGrid:DataGridTemplateColumn.CellContentTemplate>
</telerikGrid:DataGridTemplateColumn>
</telerikGrid:RadDataGrid.Columns>
</telerikGrid:RadDataGrid>
Code behind:
public ViewModel AvailableVM { get; set; }
public MainPage()
{
this.InitializeComponent();
AvailableVM = new ViewModel();
}
public class ViewModel
{
public void Testmethod()
{
}
public ObservableCollection<DataTest> PickListItems { get; set; }
public ViewModel()
{
PickListItems = new ObservableCollection<DataTest>()
{
new DataTest { Country = "Brazil", City = "Caxias do Sul", ListSelectedCommand = new RelayCommand(()=>{ })},
new DataTest { Country = "Ghana", City = "Wa", ListSelectedCommand = new RelayCommand(Testmethod)},
new DataTest { Country = "Brazil", City = "Fortaleza"}
};
}
}
public class DataTest
{
public string City { get; set; }
public string Country { get; set; }
public ICommand ListSelectedCommand { get; set; }
}
class RelayCommand : ICommand
{
public event EventHandler CanExecuteChanged;
private Action _action;
public RelayCommand(Action action)
{
this._action = action;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
this._action();
}
}
By the way, the CellContentTemplate may have influences on binding. Tried to bind on the above way. Also the RadDataGrid has its own commands you can refer if there is one suit your scenario.
Based from this XAML,
<ListView>
<ListView.View>
<GridView>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal"
VerticalAlignment="Center"
HorizontalAlignment="Center">
<TextBlock Text="{Binding ProductDescription}"/>
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal"
VerticalAlignment="Center"
HorizontalAlignment="Center">
<TextBox x:Name"txtExchange"
Tag="{Binding ProductBarcode}"/>
<Button Content="Add"
Tag="{Binding ProductBarcode}"
Click="SelectExchangeProduct" />
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
since all rows inserted will have the same TextBox, using x:Name to get the value is impossible.
I added binding to the Tag property so that I can differentiate all the TextBox. The problem is how can I find that specific TextBox? I tried using FindName(), but I don't know how to get that TextBox with a specific Tag value.
I also tried this:
var txtExchangeQuantity = someParentControl.Children.OfType<TextBox>().Where(x => x.Tag.ToString() == barcode).FirstOrDefault();
but also didn't work. I saw potential to that Where part, but don't know how to implement it.
Here is the Click event:
private void SelectExchangeProduct(object sender, RoutedEventArgs e)
{
Button btn = sender as Button;
string barcode = btn.Tag.ToString();
var txtExchangeQuantity = grdExchange.Children.OfType<TextBox>().Where(x => x.Tag.ToString() == barcode).FirstOrDefault();
}
Here is the object that I'm binding to (using ObservableCollection):
class TransactionList{
private string _productBarcode;
public string ProductBarcode
{
get { return _productBarcode; }
set { _productBarcode = value; }
}
private string _productDescription;
public string ProductDescription
{
get { return _productDescription; }
set { _productDescription = value; }
}
}
Create an implementation of ICommand somewhere in your code like this:
public class RelayCommand : ICommand
{
#region Fields
private Action<object> execute;
private Predicate<object> canExecute;
#endregion
#region Events
public event EventHandler CanExecuteChanged;
#endregion
#region Constructors
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
this.execute = execute ?? throw new ArgumentException(nameof(execute));
this.canExecute = canExecute ?? throw new ArgumentException(nameof(canExecute));
}
#endregion
#region Methods
public void InvokeExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
public bool CanExecute(object parameter)
{
return canExecute(parameter);
}
public void Execute(object parameter)
{
execute(parameter);
}
#endregion
}
You can then implement a property in your view model like
public RelayCommand SelectExchangeProductCommand { get; private set; }
public ViewModel()
{
SelectExchangeProductCommand = new RelayCommand(SelectExchangeProductExecute, SelectExchangeProductCanExecute);
}
and then have the two methods implemented:
public void SelectExchangeProductExecute(object parameter)
{
// parameter will then be your Barcode
if(parameter is string barcode)
{
// Now you have the barcode available... but you can really bind anything to end up here.
}
}
public bool SelectExchangeProductCanExeucte(object parameter)
{
// return true and false accordingly to execution logic. By default, just return true...
return true;
}
Then you can bind the Button to this command in xaml like the following:
<DataTemplate>
<StackPanel Orientation="Horizontal"
VerticalAlignment="Center"
HorizontalAlignment="Center">
<TextBox x:Name"txtExchange" />
<Button Content="Add"
Command="{Binding Source=ViewModel, Path=SelectExchangeProductCommand}"
CommandParameter="{Binding ProductBarcode}" />
</StackPanel>
</DataTemplate>
And like this you do not need to identify the Button or TextBox, just bind the respective data to CommandParameter and the template will do the rest.
Seems ICommand is the way to go but this is a new concept to me so I'll tell you what would I do in your case:
Group controls in a UserControl "ExchangeControl" with eg. a TextBox "txtExchange", a Button "btnAdd" and an event so when you click btnAdd, you'll have full control to do something with txtExange.
Instantiate it when needed:
someStackPanel.Children.Add(new ExchangeControl() {Content = "SOME TEXT"});
To let the above code work, you should have this property in your ExchangeControl class:
private string content;
public string Content
{
get { return content; }
set { content = value; txtExchange.Text = value; }
}
So... now you can get txtExchange content (SOME TEXT) by clicking btnAdd if you created an event to the button click in your ExchangeControl class
Just to graph, here's an example of adding an event to your button assuming there's already a StackPanel with some ExchangeControl items
someStackPanel.Children.Cast<ExangeControl>().ToList().ForEach(ec =>
ec.btnAdd.Click += (se, a) => MessageBox.Show(ec.txtExchange.Text));
By the way, here's how to get content of specific TextBox in a DataGrid when selected:
string someContent = (yourDataGrid.SelectedItem as TextBox).Text;
It is easier by using VisualTreeHelper to get Children by type.
How to get children of a WPF container by type?
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>