Dynamically Adding event handlers to WPF - c#

I'm looking for help with an app I'm building. I've an xml file being read into an app. This XML is of the following structure:
`<Tabs>
<Tab>
<Search name="ListSearch" Title="SearchHeader">
<Label Name="lblSchema"></Label>
<ComboBox Name="comboxSchema" Visibility="Visible" IsEnabled="True" ItemSource="{Binding AvailableSchema}" SelectedValue="{Binding SelectedSchema}" />
<ComboBox Name="comboxList" Visibility="Visible" IsEnabled="True" ItemSource="{Binding AvailableList}" SelectedValue="{Binding SelectedList}" />
<Label Name="lblCriteria"></Label>
<ComboBox Name="comboxFields" Visibility="Visible" IsEnabled="True" ItemSource="{Binding AvailableFields}" SelectedValue="{Binding SelectedField}" />
<ComboBox Name="comboxOperator" Visibility="Visible" IsEnabled="True" ItemSource="{Binding Operations}" SelectedValue="{Binding SelectedOperator}" />
<TextBox Name="txtBoxInputValue" Visibility="Visible" IsEnabled="True" Text="{Binding InputValue}" />
<CustomControl type="DatePicker"></CustomControl>
<Button Name="btnAddQueryLine" Content="{Binding TextOnAddQueryButton}" Command="{Binding CamlAddQueryLine}" Action="Publish"></Button>
<Button Name="btnPasteQueryLine" Content="{Binding TextOnPasteQueryButton}" Command="{Binding CamlPasteQueryLine}" Action="Preview"></Button>
<Button Name="btnRemoveQueryLine" Content="{Binding TextOnRemoveQueryButton}" Command="{Binding CamlRemoveQueryLine}" Action="UnPublish"></Button>
<Button Name="btnClearQuery" Content="{Binding TextOnClearQueryButton}" Command="{Binding CamlClearQuery}" Action="UnPreview"></Button>
<Button Name="btnCamlSearch" Content="{Binding TextOnSearchQueryButton}" Command="{Binding CamlSearch}" Action="Meh"></Button>
<Button Name="btnCloseSearch" Content="{Binding TextOnCloseQueryButton}" Command="{Binding CloseSearch}" Action="NewMeh"></Button>
</Search>
</Tab>
</Tabs>`
So I read in the xml, and use methods like this to add buttons etc to the ribbon:
private void AddButtonToGroup(string header,RibbonGroup group,string command,string action)
{
RibbonButton button = new RibbonButton();
button.Label = header;
button.Name = action;
button.Click += new RoutedEventHandler(button_Click);
group.Items.Add(button);
}
with the following event handler:
void button_Click(object sender, RoutedEventArgs e)
{
Button clicked = (Button)sender;
MessageBox.Show("Button Clicked!");
MessageBox.Show("Performing Action:" + clicked.Name);
}.
The problem I have, is that this isn't the requirement- the event handler is hard coded. Is there any way to create event handlers dynamically? Can anyone point me in the right direction?

You could create a method that returns an Action<object, RoutedEventArgs>:
private Action<object, RoutedEventArgs> MakeButtonClickHandler()
{
return (object sender, RoutedEventArgs e) =>
{
// Put your code here, it will be called when
// the button is clicked
};
}
So MakeButtonClickHandler returns a new anonymous function each time it's called. Then assign it like this:
button.Click += new RoutedEventHandler(MakeButtonClickHandler());
Another way of accomplishing the same thing is to do this inline, like so:
button.Click += (object sender, RoutedEventArgs e) =>
{
// Put your code here
};
For some more information, take a look at Anonymous Functions on MSDN.

You need to define a set of actions that the user will be able to do. Then assign a name to each one of them and implement the actions in code.
For example, the XML would be:
<Button Content="TextOnAddQueryButton" Command="CamlAddQueryLine"></Button>
In code, you would assign the event handler like this:
private void AddButtonToGroup(string header,RibbonGroup group,string command,string action)
{
RibbonButton button = new RibbonButton();
button.Tag = command;
//...
}
And the handler for the button would be:
void button_Click(object sender, RoutedEventArgs e)
{
Button clicked = (Button)sender;
switch (clicked.Tag)
{
case "CamlAddQueryLine":
// Call action for CamlAddQueryLine
break;
}
}

Related

Button inside button (WPF + Caliburn.Micro)

In regular WPF you can have a button inside another button, and to prevent both button events being raised when you click the inner button, you can have the following XAML (As per this question):
<Button Click="OuterClick">
<Grid>
<SomeOtherContent />
<Button Click="InnerClick" />
</Grid>
</Button>
And then in the code-behind use the Handled property on the event so that OuterClick is not raised when the inner button has been clicked:
private void OuterClick(object sender, RoutedEventArgs e) {
// Do something
}
private void InnerClick(object sender, RoutedEventArgs e) {
// Do something else
e.Handled = true;
}
However, following the usual Caliburn.Micro conventions you'll have the following XAML:
<Button x:Name="OuterClick">
<Grid>
<SomeOtherContent />
<Button x:Name="InnerClick" />
</Grid>
</Button>
With this in the ViewModel:
private void OuterClick() {
// Do something
}
private void InnerClick() {
// Do something else
}
How can I make sure that not both InnerClick() and OuterClick() are raised when I click the InnerButton?
EDIT: Or, what other kind of controls can I use instead?
<StackPanel cal:Message.Attach="[Event MouseDown]=[Action OuterClick()]">
<StackPanel Orientation="Horizontal">
<SomeotherContent />
<Button x:Name="InnerClick" />
</StackPanel>
</StackPanel>
In Your viewmodel
public void OuterClick()
{
//Do something
}
public void InnerClick()
{
//Do something
}
use stackpanel and apply event for that one.
This will help you.

Binding Click action to custom xaml component

I have the following custom control in XAML:
<Button x:Name="button" HorizontalAlignment="Left" Margin="0,0,0,0" VerticalAlignment="Center" Width="200">
<StackPanel Orientation="Horizontal" Width="170">
<TextBlock Text="" FontFamily="Segoe UI Symbol" Visibility="{Binding isFolder, Converter={StaticResource BooleanToVisibilityConverter}}"/>
<TextBlock Text="" FontFamily="Segoe UI Symbol" Foreground="Gold" Visibility="{Binding isFolder, Converter={StaticResource InverseBooleanToVisibilityConverter}}"/>
<TextBlock Text="{Binding title}" Margin="5,0" Foreground="Black"/>
</StackPanel>
I want to bind click action on isFolder. So I created a xaml page and in code behind I added the control as follow:
MyControl item = new MyControl();
item.DataContext = context;
// item.click += context.isFolder ? folderAction : nonFolderAction
There is no item.click. How can I add delegate to click based on boolean value?
You can use relaycommands where you can specify the condition when the command should be executed.
For example: (in your case)
In your viewmodel: (do this)
private RelayCommand myCommandInViewModel;
public RelayCommand MyCommandInViewModel
{
get { return myCommandInViewModel?? (myCommandInViewModel= new RelayCommand(myCommandInViewModelAction,()=> { return isFolder; })); }
}
where myCommandInViewModelAction has your method definition.
Above is the advised way of doing.
Else you can have another way where in the button.Click event you can get the datacontext of the sender and check for isFolder property in it.
private void someClickEvent(object sender, RoutedEventArgs e)
{
var buttonDataContext = (sender as Button).DataContext;
if(buttonDataContext.isFolder)
{
doSomeThing();
}
else
{
return;
}
}

WPF multiple buttons, same click function but different parameter

I want to make several buttons with the same click function but different parameter. Like this
XAML:
<Button Click="MyClickFunction(1)" Content="MyFunc(1)"/>
<Button Click="MyClickFunction(2)" Content="MyFunc(2)"/>
<Button Click="MyClickFunction(3)" Content="MyFunc(3)"/>
<Button Click="MyClickFunction(4)" Content="MyFunc(4)"/>
C#:
private void MyClickFunction(object sender, RoutedEventArgs e, int myParam)
{
//do something with parameter e.g. MessageBox.Show()
MessageBox.Show(myParam.ToString());
}
How to I pass the parameter in xaml tag or I will have to do it other ways?
Use Tag property in the button. Like this:
<Button Click="MyClickFunction" Tag="1" Content="MyFunc(1)"/>
<Button Click="MyClickFunction" Tag="2" Content="MyFunc(2)"/>
<Button Click="MyClickFunction" Tag="3" Content="MyFunc(3)"/>
<Button Click="MyClickFunction" Tag="4" Content="MyFunc(4)"/>
And in the code behind:
private void MyClickFunction(object sender, RoutedEventArgs e)
{
var tag = ((Button)sender).Tag;
MessageBox.Show(tag.ToString());
}

Windows phone 8.1 button click arguments

Is there a way to pass arguments on button click in Windows Phone 8.1?
I have a grid of 5x5 buttons, and they should all call the same method but with a different parameter. I am adding a handler like this:
foreach (var child in buttonGrid.Children)
{
Button b = child as Button;
if (b != null)
{
b.Click += Button_Click;
// I want to add an argument to this
}
}
Now the only way I can get the index of the button is by iterating over the whole grid and checking if the sender is equal to the button:
private void Button_Click(object sender, RoutedEventArgs e)
{
for (int i = 0; i < buttonGrid.Children.Count; i++)
{
if (sender == buttonGrid.Children[i])
{
DoSomething(i);
return;
}
}
}
It works, but I don't really like this approach. Is there a more efficient way of doing this (other than creating a different method for each of the 25 buttons)?
I tried searching on the internet, but the documentation and examples for Windows Phone are really lacking. If anyone has a good repository of Windows Phone 8.1 tutorials to direct me to, that would also be of help.
You can use Tag property of the button.
For eg.
I'm trying to create a number pad which has 9 buttons with the respective number as the button content and i have set the same thing as the Tag property also.
<StackPanel>
<StackPanel Orientation="Horizontal" >
<Button Content="1" Margin="10" HorizontalAlignment="Left" VerticalAlignment="Top" FontSize="48" FontWeight="Bold" Click="Button_Click" Tag="1" />
<Button Content="2" Margin="10" HorizontalAlignment="Left" VerticalAlignment="Top" FontSize="48" FontWeight="Bold" Click="Button_Click" Tag="2" />
<Button Content="3" Margin="10" HorizontalAlignment="Left" VerticalAlignment="Top" FontSize="48" FontWeight="Bold" Click="Button_Click" Tag="3" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<Button Content="4" Margin="10" HorizontalAlignment="Left" VerticalAlignment="Top" FontSize="48" FontWeight="Bold" Click="Button_Click" Tag="4" />
<Button Content="5" Margin="10" HorizontalAlignment="Left" VerticalAlignment="Top" FontSize="48" FontWeight="Bold" Click="Button_Click" Tag="5" />
<Button Content="6" Margin="10" HorizontalAlignment="Left" VerticalAlignment="Top" FontSize="48" FontWeight="Bold" Click="Button_Click" Tag="6" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<Button Content="7" Margin="10" HorizontalAlignment="Left" VerticalAlignment="Top" FontSize="48" FontWeight="Bold" Click="Button_Click" Tag="7" />
<Button Content="8" Margin="10" HorizontalAlignment="Left" VerticalAlignment="Top" FontSize="48" FontWeight="Bold" Click="Button_Click" Tag="8" />
<Button Content="9" Margin="10" HorizontalAlignment="Left" VerticalAlignment="Top" FontSize="48" FontWeight="Bold" Click="Button_Click" Tag="9" />
</StackPanel>
</StackPanel>
This produces the following output :
In your code behind you can now use the Tag property in the following manner
private void Button_Click(object sender, RoutedEventArgs e)
{
var tag = (sender as Button).Tag;
int t = Convert.ToInt16(tag);
switch (t)
{
case 1:
//Do Something
break;
case 2:
//Do Something
break;
case 3:
//Do Something
break;
case 4:
//Do Something
break;
case 5:
//Do Something
break;
case 6:
//Do Something
break;
case 7:
//Do Something
break;
case 8:
//Do Something
break;
case 9:
//Do Something
break;
default:
break;
}
}
Controls and other elements in XAML have a Tag property that you can set to an arbitrary object value for this kind of thing. Set it when you create the object and then inspect it in the event handler.
A simple solution would be to assign unique numbers to each button and then call the function with this uniquely assigned number as a parameter.
In the function, you could easily use the If-Else blocks to perform the task according to the Unique Number.
The code for 4 buttons becomes something like:-
void func(int unique_number)
{
if(unique_number==1)
{
//perform tasks for button 1
}
if(unique_number==2)
{
//perform tasks for button 2
}
if(unique_number==3)
{
//perform tasks for button 3
}
if(unique_number==4)
{
//perform tasks for button 4
}
}
private void Button_1_Click(object sender, RoutedEventArgs e)
{
func(1); //Call from button 1
}
private void Button_2_Click(object sender, RoutedEventArgs e)
{
func(2); //Call from button 2
}
private void Button_3_Click(object sender, RoutedEventArgs e)
{
func(3); //Call from button 3
}
private void Button_4_Click(object sender, RoutedEventArgs e)
{
func(4); //Call from button 4
}
I hope this makes it somewhat easy and efficient.
While the answers show you how to do this using Tag, this approach is non-convenient for, say 100 buttons.
To achieve your result, you can use closures for the job. Here's how:
// When you're attaching handlers
int i=0;
foreach (var child in buttonGrid.Children)
{
Button b = child as Button;
if (b != null)
{
b.Click += () => {
int z = i++; // You can put a different id generator here as well, like int z = <rand>%<prime> if you want
DoSomething(z); // DoSomething called with corrosponding button id
}
}
}
This approach does not require you to use tags!

How to select an item in a ListBox and pick the item's name from a DataTemplate in WPF

I am implementing a Download UI in WPF, where every file that is being downloaded will be shown inside a list box in a DataTemplate
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock x:Name="FileName" text={Binding FileName}" />
<ProgressBar ... />
<Button Content="Cancel" click="ButtonCancel_Click" />
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox>
Now this List is getting populated with all the download information perfectly. Only problem I am having is that when user clicks on Cancel button, to cancel the download, I have to remove an entry from the ObservableCollections. But I don't have the File Name in the click event( I know click event is not MVVM, still I want to do it in click event handler).
Can anyone suggest how do I get the FileName of that particular file when the selectedItem gets cancelled. in The
private void ButtonCancel_Click(...) {}
Although I would still encourage you to use MVVM way of dealing with UI events, here's how you can achieve what you want, using Cancel button's click event handler.
First in your xaml, bind file name to Cancel button's Tag property.
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock x:Name="FileName" text={Binding FileName}" />
<ProgressBar ... />
<Button Content="Cancel" Tag="{Binding FileName}"
Click="ButtonCancel_Click" />
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox>
Then in your click event handler
private void ButtonCancel_Click(object sender, RoutedEventArgs e)
{
Button myButton = (Button)sender;
string fileName = myButton.Tag.ToString();
// use fileName
}
Edit
Just to add a complete example, that was tested locally, and ensured that works.
XAML
<Window x:Class="WpfTestApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListBox Name="listBox1">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock x:Name="FileName" Text="{Binding Path=FileName}" />
<Button Content="Cancel" Tag="{Binding Path=FileName}"
Click="ButtonCancel_Click" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
Code-behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var fileNames = new List<DownloadModel>
{
new DownloadModel
{
FileName = "File1"
},
new DownloadModel
{
FileName = "File2"
},
new DownloadModel
{
FileName = "File3"
}
};
listBox1.ItemsSource = fileNames;
}
private void ButtonCancel_Click(object sender, RoutedEventArgs e)
{
var myButton = sender as Button;
if (myButton.Tag == null)
{
MessageBox.Show("Tag value was null.");
}
else
{
MessageBox.Show(string.Format("File name is {0}", myButton.Tag));
}
}
}
public class DownloadModel
{
public string FileName { get; set; }
}

Categories