Custom Caption Buttons in WPF - c#

I am creating a custom window style in WPF. So far, I have been able to accomplish something that almost works, in pure XAML.
<ControlTemplate TargetType="{x:Type Window}">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}">
<DockPanel LastChildFill="True">
<Border Height="40" Padding="5" DockPanel.Dock="Top" BorderBrush="#7FA0A0A0"
BorderThickness="0,0,0,1">
<Grid WindowChrome.IsHitTestVisibleInChrome="True">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<Image Source="Resources/Logo/f-dark.png" />
<TextBlock Margin="0,0,0,0" Text="{TemplateBinding Title}" VerticalAlignment="Center"
FontSize="16" Foreground="#AF000000"/>
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right"
WindowChrome.IsHitTestVisibleInChrome="True" Background="Transparent">
<Button Width="{x:Static SystemParameters.WindowCaptionButtonWidth}"
Command="{x:Static SystemCommands.MinimizeWindowCommand}"
Style="{StaticResource LinkButton}"
WindowChrome.IsHitTestVisibleInChrome="True"
IsEnabled="True">
<TextBlock Style="{StaticResource Icon}" FontSize="18"></TextBlock>
</Button>
<Button Width="{x:Static SystemParameters.WindowCaptionButtonWidth}"
Command="{x:Static SystemCommands.RestoreWindowCommand}"
Style="{StaticResource LinkButton}"
WindowChrome.IsHitTestVisibleInChrome="True"
IsEnabled="True">
<TextBlock Style="{StaticResource Icon}" FontSize="18"></TextBlock>
</Button>
<Button Width="{x:Static SystemParameters.WindowCaptionButtonWidth}"
Command="{x:Static SystemCommands.CloseWindowCommand}"
Style="{StaticResource LinkButton}"
WindowChrome.IsHitTestVisibleInChrome="True"
IsEnabled="True">
<TextBlock Style="{StaticResource Icon}" FontSize="18"></TextBlock>
</Button>
</StackPanel>
</Grid>
</Border>
<Border>
<ContentPresenter/>
</Border>
</DockPanel>
</Border>
</ControlTemplate>
However, the in the window chrome buttons do not highlight or change the cursor (and buttons with the same style behave as intended). Clicking on them has no effect. Why is this? How do I get the buttons to be interactive?
When I first open the window, I get something like this:
I would also like to be able to fix the black border, but my main problem is the fact that the caption buttons are noninteractive.

I have solved both problems. It turns out that the SystemCommands do not have implementations, and instead are just RoutedCommands. (why did they do that?)
So, I made an attached property that sets the CommandBindings for each window that I apply this style to.
public class Helper
{
public static bool GetUseWindowCommandBindings(DependencyObject obj)
{
return (bool)obj.GetValue(UseWindowCommandBindingsProperty);
}
public static void SetUseWindowCommandBindings(DependencyObject obj, bool value)
{
obj.SetValue(UseWindowCommandBindingsProperty, value);
}
public static readonly DependencyProperty UseWindowCommandBindingsProperty =
DependencyProperty.RegisterAttached("UseWindowCommandBindings", typeof(bool), typeof(Helper), new PropertyMetadata(false, UseWindowCommandBindingsChanged));
private static void UseWindowCommandBindingsChanged(DependencyObject d, DependencyPropertyChangedEventArgs dpce)
{
if (d is Window w && dpce.NewValue is bool b && b)
{
w.CommandBindings.Add(
new CommandBinding(
SystemCommands.MinimizeWindowCommand,
(s, e) => SystemCommands.MinimizeWindow(w),
(s, e) => e.CanExecute = true
));
w.CommandBindings.Add(
new CommandBinding(
SystemCommands.RestoreWindowCommand,
(s, e) => SystemCommands.RestoreWindow(w),
(s, e) => e.CanExecute = true
));
w.CommandBindings.Add(
new CommandBinding(
SystemCommands.MaximizeWindowCommand,
(s, e) => SystemCommands.MaximizeWindow(w),
(s, e) => e.CanExecute = true
));
w.CommandBindings.Add(
new CommandBinding(
SystemCommands.CloseWindowCommand,
(s, e) => SystemCommands.CloseWindow(w),
(s, e) => e.CanExecute = true
));
}
}
}
I had to put the attachment of the CommandBindings in the property-change handler since WPF calls SetValue() directly. Now, in my style, I added the line:
<Setter Property="view:Helper.UseWindowCommandBindings" Value="True" />
Now the buttons work as expected. To fix the black border, I set the width of the outermost Border in my template to {TemplateBinding Width}. But this creates the problem of having a massive, all-around black border if I maximize the window:

Related

How to change the background color of TreeViewItem when clicking in the WPF treeview?

I want to change the background of StackPanel when clicking TextBlock.
enter image description here
now background is bule! I want to change it!
<TreeView Name="tvView" HorizontalAlignment="Left" VerticalAlignment="Top" BorderThickness="0">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=ChildNodes}">
<Grid VerticalAlignment="Top" Margin="0">
<StackPanel Orientation="Horizontal" d:LayoutOverrides="Height">
<TextBlock Name="ConfidenceLevelReminderText" Text="!" FontWeight="Bold" HorizontalAlignment="Center" Foreground="#FF0000" Width="{Binding Path=ConfidenceLevelWidth}" Margin="0,0,0,0"></TextBlock>
<TextBlock TextWrapping="Wrap"
Padding="0,3,0,3"
Text="{Binding Path=Name}"
ToolTip="{Binding Path=NameToolTip}"
Tag="{Binding Path=TagInfo}"
MouseLeftButtonDown="name_MouseLeftButtonDown"
FontSize="14"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Foreground="#FF666666"
Margin="0"
Width="{Binding Path=TextWidth}"
Cursor="Hand"/>
<Button x:Name="btnIngnore" Width="{Binding Path=IgnoreWidth}" Click="btn_ignore_Click" Tag="{Binding Path=NodeId}" Margin="0" BorderThickness="0" Background="{x:Null}" Style="{StaticResource ButtonStyle32}" IsEnabled="{Binding Path=IgnoreWidth,Converter={StaticResource itb}}" Cursor="Hand">
<TextBlock Text="xxxx" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0" FontSize="12" Foreground="#FF666666"/>
</Button>
</StackPanel>
</Grid>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
If you want
to change the background of StackPanel when clicking TextBlock.
You can add EventSetter to the TextBlock's Style and handle event in the event handler.
<TextBlock Text="...">
<TextBlock.Style>
<Style TargetType="TextBlock">
<EventSetter Event="MouseDown" Handler="Mouse_LBtnDown"/>
</Style>
</TextBlock.Style>
</TextBlock>
private void Mouse_LBtnDown(object sender, MouseButtonEventArgs e)
{
StackPanel stp = null;
var visParent = VisualTreeHelper.GetParent(sender as FrameworkElement);
while (stp == null && visParent != null)
{
stp = visParent as StackPanel;
visParent = VisualTreeHelper.GetParent(visParent);
}
if (stp == null) { return; }
stp.Background = Brushes.Coral;
}
For make it reusable and adjustable you could make a behavior from the event handler.
If you don't want what you have asked, then rethink your question and ask new one.

Capture Click event of button with image in WPF

I'm new to WPF and created a draggable button with an image inside - which works nicely...but I can't seem to capture the onClick of the button which contains the image?
<Window x:Class="PAD.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:PAD"
mc:Ignorable="d"
Title="C2D" Height="134" Width="134" WindowStyle="None" Background="Transparent" Foreground="Transparent" BorderBrush="Transparent" BorderThickness="0" ResizeMode="NoResize" AllowsTransparency="True">
<Grid x:Name="ClickIcon" Background="Transparent" >
<Button x:Name="dialButton" HorizontalAlignment="Left" VerticalAlignment="Top" Width="130" Height="130" Foreground="Transparent" Background="Transparent" BorderThickness="0" BorderBrush="Transparent" >
<Button.Template>
<ControlTemplate TargetType="{x:Type Button}">
<Grid>
<Ellipse Fill="Transparent"/>
<ContentPresenter Content="{TemplateBinding Content}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<Image x:Name="phoneIcon" Width="128" Height="128" Source="c:\users\chapmd1\documents\visual studio 2015\Projects\PAD\PAD\red-phone-icon_300205.png" MouseDown="image_MouseDown" />
</Grid>
</ControlTemplate>
</Button.Template>
</Button>
private void image_MouseDown(object sender, MouseButtonEventArgs e)
{
this.DragMove();
}
I tried the onClick on the Button, but nothing got captured?
Not sure what you want to achieve, but in case you want to Drag the Window while the button is pressed and then afterwards do something else then you can do it like this:
private void image_MouseDown(object sender, MouseButtonEventArgs e)
{
while (e.ButtonState == MouseButtonState.Pressed)
{
this.DragMove();
Debug.WriteLine("Dragged!");
}
var image = sender as Image;
object parent = FindParent(image);
while (parent != null && parent.GetType() != typeof(Button))
{
parent = FindParent((FrameworkElement)parent);
}
var bt = parent as Button;
if(bt != null)
bt.ClickMode = ClickMode.Press;
}
private void DialButton_OnClick(object sender, RoutedEventArgs e)
{
Debug.WriteLine("Clicked!");
}
DependencyObject FindParent(FrameworkElement ui)
{
return ui.TemplatedParent;
}
}
Your problem is probably somewhere else. This works as expected:
XAML:
<Grid x:Name="ClickIcon" Background="Transparent" >
<Button
Click="dialButton_Click"
x:Name="dialButton"
HorizontalAlignment="Left"
VerticalAlignment="Top" Width="130" Height="130"
Foreground="Transparent"
Background="Transparent"
BorderThickness="0"
BorderBrush="Transparent">
<Button.Template>
<ControlTemplate TargetType="{x:Type Button}">
<Grid >
<Ellipse Fill="Transparent"/>
<ContentPresenter
Content="{TemplateBinding Content}"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
<Image x:Name="phoneIcon"
Width="128" Height="128"
Source="..."
MouseDown="phoneIcon_MouseDown"/>
</Grid>
</ControlTemplate>
</Button.Template>
</Button>
</Grid>
C#:
private void phoneIcon_MouseDown(object sender, MouseButtonEventArgs e)
{
System.Diagnostics.Trace.WriteLine("phoneIcon_MouseDown");
}
private void dialButton_Click(object sender, RoutedEventArgs e)
{
System.Diagnostics.Trace.WriteLine("dialButton_Click");
}

Set Progress bar visibility in button click inside the datatemplate

I am trying to set the progress bar visibilty in the button click, where both are inside the data template. I can't set the name for the progress bar and sent the visibility, since it is in a template.
Is there any method to achieve this. Following code is what I have tried.
<DataTemplate>
<Grid>
<Button Click="Image_Download" Loaded="Button_Loaded" Tag="{Binding .}" Width="80" Height="80" >
<Button.Template>
<ControlTemplate TargetType="Button">
<Border HorizontalAlignment="Center" VerticalAlignment="Center" >
<ContentControl Content="{TemplateBinding Content}"/>
</Border>
</ControlTemplate>
</Button.Template>
<Image Source="c_image.png" Tag="{Binding .}" />
</Button>
<ProgressBar VerticalAlignment="Bottom" IsIndeterminate="true" Visibility="Collapsed" Style="{StaticResource CustomIndeterminateProgressBar}" />
</Grid>
</DataTemplate>
You can do this
private void Image_Download(object sender, RoutedEventArgs e) {
Button button = sender as Button;
if (button != null) {
Grid grid = button.Parent as Grid;
if (grid != null) {
ProgressBar progressBar = grid.Children.OfType<ProgressBar>().FirstOrDefault();
if (progressBar != null) {
progressBar.Visibility = Visibility.Hidden;
}
}
}
}

Custom control - Listview with no predefined content WPF/c#

I used for the first time a custom Control (wpf). Here is what I want:
I want a Custom ListView with Custom ListViewItem (the challenge is I don’t know what’s on the “Form”, I just know it’s a Panel - I call "Form" what's on the ListViewItem ).
I have a CustomListView and a CustomListViewItem :
EDIT : CustomListView.cs
public class CustomListView : ItemsControl
{
public static readonly DependencyProperty ListContentProperty = DependencyProperty.Register("ListContent", typeof(IList), typeof(CustomListView), new PropertyMetadata());
public IList ListContent
{
get { return (IList)GetValue(ListContentProperty); }
set { SetValue(ListContentProperty, value); }
}
public CustomListView(ContentControl contentControl, IList list)
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomListView), new FrameworkPropertyMetadata(typeof(CustomListView)));
DataContext = list;
ItemsSource = list;
CustomListViewItem customListViewItem = new CustomListViewItem();
customListViewItem.Content = contentControl;
string xamlTemplate = XamlWriter.Save(customListViewItem);
string template =
"<DataTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'>" +
xamlTemplate +
"</DataTemplate>";
ItemTemplate = (DataTemplate)XamlReader.Parse(template);
SetValue(TemplateContentProperty, contentControl);
}
}
EDIT : CustomListViewItem .cs
public class CustomListViewItem : ContentControl
{
public static readonly DependencyProperty MyEventProperty = DependencyProperty.Register("MyEvent", typeof(EventHandler), typeof(CustomListViewItem));
public static readonly DependencyProperty ListContentItemProperty = DependencyProperty.Register("ListContentItem", typeof(IList), typeof(CustomListViewItem), new PropertyMetadata());
public IList ListContentItem
{
get { return (IList)GetValue(ListContentItemProperty); }
set { SetValue(ListContentItemProperty, value); }
}
public EventHandler MyEvent
{
get { return (EventHandler)GetValue(MyEventProperty); }
set { SetValue(MyEventProperty, value); }
}
public CustomListViewItem()
{
DefaultStyleKey = typeof(CustomListViewItem);
}
//Some method for the Event on my button
The DataContext of the CustomListView is an ObservableCollection. When I create the “Form” directly on my CustomListViewItem, everything is perfect. I can sort with my button, add a new empty Form…
Now here is my problem.
Let’s say we have a class Person (with only a name) who implements INotifyPropertyChanged. We create some “Person” and add them to an ObservableCollection. On a new xaml element, we create a Template (PersonEditor) the “Form”, with some Binding.
What I want on the MainWindows is something like that:
CustomListView customListView = new CustomListView(new PersonEditor(), _person);
myContentControl.Content = customListView;
Where _person is the ObservableCollection. We give the Template of the Form to the CustomListViewItem and add it to a content of a ContentControl
EDIT : Generics.xaml :
<Style x:Key="{x:Type customControl:CustomListView}" TargetType="customControl:CustomListView" BasedOn="{StaticResource {x:Type ItemsControl}}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type customControl:CustomListView}">
<Border>
<ItemsPresenter />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="{x:Type customControl:CustomListViewItem}" TargetType="customControl:CustomListViewItem" BasedOn="{StaticResource {x:Type ContentControl}}">
<Setter Property="ListContentItem">
<Setter.Value>
<Binding Path="ListContent" RelativeSource="{RelativeSource FindAncestor,AncestorType=customControl:CustomListView}"></Binding>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="customControl:CustomListViewItem">
<Border Background="White" x:Name="borderSelect" Padding="0" Width="Auto" Height="Auto">
<DockPanel Width="Auto" Height="Auto" VerticalAlignment="Stretch">
<ContentPresenter/>
<StackPanel Orientation="Horizontal">
<StackPanel SnapsToDevicePixels="true" Orientation="Vertical" Margin="0">
<StackPanel SnapsToDevicePixels="true" Orientation="Horizontal" Margin="0">
<Button SnapsToDevicePixels="true" Style="{StaticResource SquareCorner}" Opacity="0.0" Margin="0 7 0 0" Height="30" Width="12" VerticalAlignment="top" HorizontalAlignment="Right" Content="x" Padding="0" x:Name="DeleteButton" Command="{TemplateBinding MyEvent}"/>
<StackPanel SnapsToDevicePixels="true" Orientation="Vertical">
<Button Style="{StaticResource RoundUpCorner}" Opacity="0.0" Margin="0 7 0 0" Height="15" Width="15" VerticalAlignment="top" HorizontalAlignment="Right" Content="^" Padding="0 0 0 5" x:Name="UpButton" Command="{TemplateBinding MyEvent}"/>
<Button Style="{StaticResource RoundDownCorner}" Opacity="0.0" Height="15" Width="15" VerticalAlignment="top" HorizontalAlignment="Right" Content="v" Padding="0" x:Name="DownButton" Command="{TemplateBinding MyEvent}"/>
</StackPanel>
</StackPanel>
</StackPanel>
<DockPanel VerticalAlignment="Stretch">
<Button Style="{StaticResource SquareLittleCorner}" DockPanel.Dock="Top" Opacity="0.0" Height="15" Width="15" VerticalAlignment="top" HorizontalAlignment="Right" Margin="1 0 0 0" Content="+" Padding="0" x:Name="AddUpButton" Command="{TemplateBinding MyEvent}"/>
<Button Style="{StaticResource SquareLittleCorner}" DockPanel.Dock="Bottom" Opacity="0.0" Height="15" Width="15" VerticalAlignment="bottom" HorizontalAlignment="Right" Margin="1 0 0 0" Content="+" Padding="0" x:Name="AddButton" Command="{TemplateBinding MyEvent}"/>
</DockPanel>
</StackPanel>
</DockPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The result is I only have one element that has the good Template, the other ones are created but empty.
I think the problem comes from the binding. But I don’t know what to do. I tried to create a new “Form” with xamlReader/xamlLoad (How can you clone a WPF object?) but with the binding again it’s not good. I also tried to clone the xaml but again, no binding and no event on button.
I don’t know what else I can do to solve this problem ? It’s my first time with a custom control so if I made a mistake (anything) just share please.
EDIT
I think my custom is better now, I follow the idea of the DataTemplate and ContentControl mix with xamlReader/xamlLoad. The Binding is good (same for Event). But, why when I used it before (like a Content not a Template) the Binding wasn't good ? And I don't know if xamlReader/xamlLoad is a good way to do that (I mean it looks more like some cheat).

Issue to Change button value dynamically in wpf

<Grid x:Name="LayoutRoot">
<Button x:Name="btn_num" Width="51" Margin="318.849,158,262.15,0" Height="45" VerticalAlignment="Top" d:LayoutOverrides="HorizontalMargin">
<Grid Height="38.166" Width="44.833">
<Label x:Name="lbl_2" Content="2" Margin="4.483,-2.042,7,-1.626" FontSize="11.333"/>
<Label x:Name="lbl_1" Content="1" Margin="4.483,0,7,-19.251" FontSize="11.333" Height="41.834" VerticalAlignment="Bottom"/>
<Label x:Name="lbl_3" Content="3" Margin="0,8.083,-15,-11.751" FontSize="11.333" HorizontalAlignment="Right" Width="33.35" Foreground="Black"/>
</Grid>
</Button>
<Button x:Name="btn_a" Content="A" HorizontalAlignment="Left" Margin="225.333,158,0,0" Width="55" Foreground="Black" Height="45" VerticalAlignment="Top" Click="btn_alt_Click" />
</Grid>
It's design will be like this
public partial class button : Window
{
static int _AClick = 0;
public button()
{
this.InitializeComponent();
}
private void btn_alt_Click(object sender, RoutedEventArgs e)
{
if (_AClick == 0)
{
_AClick = 1;
Fill();
}
else
{
btn_num.Content = "";
_AClick = 0;
}
}
public void Fill()
{
btn_num.Content = "3";
}
}
The result after window loaded
If i click A button first time. The result will be like this
If I click A button second time. The result will be like this
when I click A button second time. I need the result like below. what should I do for that.
There are a lot of ways available in WPF to achieve this. One way to do this is to have two ControlTemplates (one having all three numbers and other having just one number) and then set the template of your button in code -
<Grid x:Name="LayoutRoot">
<Grid.Resources>
<ControlTemplate
x:Key="threeNumberTemplate"
TargetType="{x:Type Button}">
<Grid Height="38.166" Width="44.833">
<Label x:Name="lbl_2" Content="2" Margin="4.483,-2.042,7,-1.626" FontSize="11.333"/>
<Label x:Name="lbl_1" Content="1" Margin="4.483,0,7,-19.251" FontSize="11.333" Height="41.834" VerticalAlignment="Bottom"/>
<Label x:Name="lbl_3" Content="3" Margin="0,8.083,-15,-11.751" FontSize="11.333" HorizontalAlignment="Right" Width="33.35" Foreground="Black"/>
</Grid>
</ControlTemplate>
<ControlTemplate
x:Key="oneNumberTemplate"
TargetType="{x:Type Button}">
<Label x:Name="lbl_3" Content="3" FontSize="11.333"/>
</ControlTemplate>
</Grid.Resources>
<Button x:Name="btn_num" Width="51" Margin="318.849,158,262.15,0" Height="45" VerticalAlignment="Top" Template="{StaticResource threeNumberTemplate}"></Button>
<Button x:Name="btn_a" Content="A" HorizontalAlignment="Left" Margin="225.333,158,0,0" Width="55" Foreground="Black" Height="45" VerticalAlignment="Top" Click="btn_alt_Click" />
</Grid>
Code behind -
private void btn_alt_Click(object sender, RoutedEventArgs e)
{
if (_AClick == 0)
{
_AClick = 1;
btn_num.Template = FindResource("oneNumberTemplate") as ControlTemplate;
}
else
{
btn_num.Template = FindResource("threeNumberTemplate") as ControlTemplate;
_AClick = 0;
}
}
Same can be achieved through triggers by making _AClick as DependecyProperty and using it's value to swap templates in triggers.
Another approach is to have two Buttons and hide/show them based on the _AClick value in code.
You can create three DataTemplate and use DataTemplateSelector class to load the corresponding data template on run time.
MSDN - DataTemplateSelector

Categories