I am new to WPF/XAML so please bear with the noob question.
I have designed a control panel that will eventually function as a backend for my website, and have just finished laying out all the buttons in tabs using TabControl element. (this is designed using the Visual Studio 'Window' forms.
My question is, is it possible to create a function in the xaml.cs file that will dynamically handle a specific event for all my button elements ? for example...
I have 30+ buttons and dont want 30 different Click="btnCustomers_click" + their respective functions in the c# code. What I desire is say one function that would allow me to click any button and then open a new window depending on which button was selected.
The below code is my current design however for 30+ buttons their will be alot of functions and it will be messy, hence my desire to have one function control which window is opened depending on which button is clicked.
private void btnMerchants_click(object sender, RoutedEventArgs e)
{
var newWindow = new frmMerchants();
newWindow.Show();
}
Thanks in advance for any advice given!! :)
You could use a style for this:
<Style TargetType="{x:Type Button}">
<EventSetter Event="Click" Handler="btnMerchants_click"/>
</Style>
If you set this up in the resources somewhere without an x:Key it will apply to all buttons.
e.g. if you have a Grid and you want a certain style to apply to all Buttons in it you would define it like this:
<Grid>
<Grid.Resources>
<Style TargetType="{x:Type Button}">
<EventSetter Event="Click" Handler="Button_Click"/>
</Style>
</Grid.Resources>
<Grid.Children>
<!-- Buttons and stuff -->
</Grid.Children>
</Grid>
If you just want to apply it to some buttons set the x:Key and reference the style:
<Grid>
<Grid.Resources>
<Style x:Key="AttachClickHandlerStyle" TargetType="{x:Type Button}">
<EventSetter Event="Click" Handler="Button_Click"/>
</Style>
</Grid.Resources>
<Grid.Children>
<Button Content="Click me!" Style="{StaticResource AttachClickHandlerStyle}"/>
<Button Content="Click me, too!" Style="{StaticResource AttachClickHandlerStyle}"/>
<Button Content="Something different." Click="SomeOtherButton_Click"/>
</Grid.Children>
</Grid>
In general you should refactor any attributes that occur more than once into a style to prevent duplicate code.
Also, since you are a beginner the following articles might be of interest:
Styling and Templating
Resources Overview
You can use routed events on the parent container.
Example:
<Grid Button.Click="GeneralHandler">
<!-- Some stuff -->
</Grid>
In the code behind:
public void GeneralHandler(object sender, RoutedEventArgs e)
{
Button b = e.OriginalSource as Button;
//<-- Do something
}
You can read more about it on MSDN.
Just assign the exact same Click="btnCustomers_click" handler (with a general function name) to all the buttons. Then in the function open the correct window based on the sender's Name.
Related
I'm trying to make a re-usable WinUI dialog to display progress information, but I want the fact that I'm using a ContentDialog to be an implementation detail and not expose its API. I figured I could do this by deriving from Control and creating a ContentDialog inside of its ControlTemplate.
Something like this:
[TemplatePart(Name = PART_Dialog, Type = typeof(ContentDialog))]
public class ProgressDialog : Control
{
private const string PART_Dialog = "PART_Dialog";
private ContentDialog _dialog;
public ProgressDialog()
{
DefaultStyleKey = typeof(ProgressDialog);
}
public async Task ShowAsync()
{
if (_dialog != null)
{
_ = await _dialog.ShowAsync(ContentDialogPlacement.Popup);
}
}
protected override void OnApplyTemplate()
{
_dialog = GetTemplateChild(PART_Dialog) as ContentDialog;
base.OnApplyTemplate();
}
}
With a style defined like so:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MyApp.Controls">
<Style TargetType="local:ProgressDialog" BasedOn="{StaticResource DefaultProgressDialog}" />
<Style x:Key="DefaultProgressDialog" TargetType="local:ProgressDialog">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:ProgressDialog">
<ContentDialog x:Name="PART_Dialog">
<Grid>
<TextBlock Text="Hello, world!" />
</Grid>
</ContentDialog>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
And then I would show the dialog like a ContentDialog:
var dialog = new ProgressDialog();
dialog.XamlRoot = this.XamlRoot;
await dialog.ShowAsync();
I have the resource dictionary specified in Generic.xaml, but the control doesn't even attempt to load the template. My OnApplyTemplate method is never called, so _dialog doesn't get wired up. I assume this is because I'm not actually creating the control in the visual tree, but then how does ContentDialog do it?
If I call ApplyTemplate() myself in ShowAsync(), it returns false and the template still isn't loaded.
How do I wrap a ContentDialog in a custom Control?
Derive from this document
Run code that can only work once the XAML-defined visual tree from templates has been applied. For example, code that obtains references to named elements that came from a template, by calling GetTemplateChild, so that members of these parts can be referenced by other post-template runtime code.
If you just implement, but not add into visual tree. OnApplyTemplate will not be invoke, and GetTemplateChild will return null. please declare in the xaml like the following.
<Grid>
<Button Click="Button_Click" Content="Open" />
<local:ProgressDialog x:Name="Dialog" />
</Grid>
Or make a class that inherit ContentDialog directly, for more please refer this document.
ListView offers a DragItemsStarting event that comes with according event args. However, unlike DragStartingEventArgs - common to other elements - it does not offer a DragUI, as far as I can tell. My only option is to use DragOver event which is very annoying.
So, I instead said I'll make the ListViewItem's content draggable. However, that backfired because now the Click event doesn't get through anymore, or only very rarely. Simply put, I either can customize the DragUI and not click my ListItems, or the DragUI looks bad, but I retain my functionality.
Is it possible to get a custom DragUI and have the click be handled by the ListView?
You can still utilize DragStarting, but you have to add it to the item content itself within your DataTemplate:
<ListView>
<ListView.ItemTemplate>
<DataTemplate>
<Grid CanDrag="True" DragStarting="ItemDragStartingHandler">
...
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
If that causes the click event not working properly, you could try customizing the ListViewItem container itself, which might work. Right-click the ListView in the designer or the Document Outline window, select Edit Additional Templates, and then Edit Generated Item Container. From the last menu select Edit a Copy...
You will get a Style which might be quite long, but within it you will find the the following:
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<ListViewItemPresenter DragStarting="ItemDragStartingHandler" x:Name="Root" ...>
You can apply DragStarting event to the ListViewItemPresenter and that might satisfy your needs.
Most of the tutorials and questions I see are about restyling the listbox to look different, but I'm interested in adding additional controls to make it behave differently. I initially started out trying to make the list builder control out of a checkbox list, but found myself too deep. I decided to abstract and start with a smaller problem.
What I am looking to do first, to get a better understanding of how this works is add "up" and "down" buttons next to the control. I think this can all be done in xaml, so to try and pressure myself to stick to that I'm working in Kaxaml.
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Page.Resources>
<!-- ListBox Order Button Style
Col 1
Listbox
Col 2
Buttons Up and Down
-->
<Style x:Key="{x:Type ListBox}" TargetType="{x:Type ListBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBox}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<Rectangle Fill="Yellow"/>
<!--<ListBox></ListBox>-->
</Grid>
<StackPanel Grid.Column="1">
<Button>Up</Button>
<Button>Down</Button>
</StackPanel>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Page.Resources>
<Grid>
<ListBox>
<TextBlock>Value 1</TextBlock>
<TextBlock>Value 2</TextBlock>
<TextBlock>Value 3</TextBlock>
<TextBlock>Value 4</TextBlock>
</ListBox>
</Grid>
</Page>
I am currently hung up on a few things.
1) When I try to use a ListBox where the Yellow Rectangle is I start getting infinite loop problems.
2) I'm not sure how to connect the buttons to the listbox once it is there. I think Triggers is the answer, but I don't have much experience with them.
Your infinite loop can be addressed by not relying on the TargetType to apply the style. Instead, apply the style explicitly via a named key (i.e. something other than {x:Type Listbox}). That way the style is applied only when you specifically want it to be applied.
"Connecting" the buttons can be done a variety of ways. The simplest would be to handle the Button.Click event and perform whatever action you want there.
All that said, I think you're going about this the wrong way. Let a ListBox be a ListBox; don't try to make it into something it's not. If you want a reusable control that adds functionality around a ListBox, like buttons to control the contents of the ListBox, you should probably be authoring a UserControl, which is essentially a composite control made up of whatever you want.
Doing so will give you a lot more control over the appearance of the control. You'll also have the opportunity to declare dependency properties on your control that are specific to exactly what that control needs to support (something you can't do just with a Style). Yes, it also means you'll have to expose properties of contained elements via new properties in your UserControl that effectively delegate to the contained elements, but that's a small price to pay for the flexibility and relative simplicity of creating the UserControl in the first place.
I've got an issue with a custom WPF button - whenever I click on it, it receives a click event but does not seem to get toggled (does not stay down when I click on another button). I've tried everything I can think of and still can't get it to work.
One weird thing that I have noticed though is that when I put a breakpoint in the MouseDown handler and just hit F5 to continue at that point, the button gets toggled and stays down. This leads me to believe that this is some sort of focus issue?
<ToggleButton Name="ToggleButton" PreviewMouseDown="ToggleButton_MouseDown_1" IsThreeState="False">
<StackPanel Orientation="Vertical">
<Label Content="Single" FontSize="15" FontWeight="Medium"/>
<Label Content="Speaker"/>
</StackPanel>
</ToggleButton>
private void ToggleButton_MouseDown_1(object sender, MouseButtonEventArgs e)
{
ToggleButton.IsChecked = !ToggleButton.IsChecked;
}
private void ToggleButton_MouseDown_1(object sender, MouseButtonEventArgs e)
{
ToggleButton.IsChecked = !ToggleButton.IsChecked;
}
Help? :)
As #Nick said in his comment, just remove your event handler PreviewMouseDown="ToggleButton_MouseDown_1" completely and it should work just fine.
If this is not the case you must have some other code which is causing the issue.
It sounds like you want the functionality of a set of radio buttons, but the look of a bunch of toggle buttons. In cases like these, using the inherited Control.Template property is really handy:
In your page resources, you can add:
<Style TargetType="RadioButton">
<Setter Property="Template">
<Setter.Value>
<ContentTemplate>
<ToggleButton Content="{TemplateBinding Content}" IsChecked="{TemplateBinding IsChecked}"/>
</ContentTemplate>
</Setter.Value>
</Setter>
</Style>
And then in your code:
<RadioButton Content="MyContent" GroupName="MyGroup"/>
This should make an object that looks like a toggle button, but deselects when you select something from the same group, like a RadioButton. You can further modify the ToggleButton in the Template until you get the look that you want.
I've created an AddressInput control for users to enter an address. The control will have a different look depending on where it's used, so I provided a DataTemplate property called AddressTemplate.
The default style looks like this:
<Style TargetType="{x:Type addressUI:AddressInput}">
<Setter Property="AddressTemplate"
Value="{StaticResource DefaultAddressTemplate}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type addressUI:AddressInput}">
<GroupBox Header="Address">
<ContentPresenter ContentTemplate="{Binding Path=AddressTemplate, RelativeSource={RelativeSource TemplatedParent}}"
Content="{Binding Path=Address, RelativeSource={RelativeSource TemplatedParent}}"
x:Name="PART_AddressPresenter" />
</GroupBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
All of my address data templates will contain a combo box for selecting the country (named "PART_CountriesList"). I need to have some code-behind action that fires when the selection changes, which means I need to hook the SelectionChanged event. Inside my AddressInput I need to find PART_CountriesList in the AddressTemplate.
I can get the "PART_AddressPresenter" ContentPresenter like this:
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
var addressPresenter = Template.FindName("PART_AddressPresenter", this) as ContentPresenter;
}
Now how do I get "PART_CountriesList" contained inside AddressTemplate?
I tried this:
var countriesList = AddressTemplate.FindName("PART_CountriesList", addressPresenter);
An exception is thrown because addressPresenter hasn't had its template applied yet. I know ContentPresenter has the OnApplyTemplate override, but it seems silly to extend it for this use.
I suppose if I were to extend ContentPresenter, I'd make a new reusable version that fires an event whenever the OnApplyTemplate method executes. This would probably solve my problem, but it seems crazy. Is there a better way?
I'm curious if someone has the "right" way to do this, but with FindName I always end up resorting to something like this:
Dispatcher.BeginInvoke( new Action(() =>
{
// Call FindName here
}), System.Windows.Threading.DispatcherPriority.Render );
This can cause flicker though because you're waiting until after the data templating is done and rendered to go exercise your code, so if what you're trying to do affects the look of the control this isn't always a great option.