I'm workin on a WPF scorekeeping app. So far the UI is done, but I can't access any of the controls in the code-behind. Here's the XAML:
<Window x:Class="Scoreboard.MainScoreBoard"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DartsLeague" Height="720" Width="1280">
<Grid x:Name="MainGrid" Background="DarkGreen">
<StackPanel x:Name="Player1" HorizontalAlignment="Left" VerticalAlignment="Stretch">
<Label x:Name="Player1Name" Style="{StaticResource PlayerName}"/>
<Label x:Name="Player1Score" Style="{StaticResource Score}"/>
<ScrollViewer x:Name="Player1Throws" Height="380" FlowDirection="RightToLeft">
</ScrollViewer>
</StackPanel>
<StackPanel x:Name="Player2" HorizontalAlignment="Right" VerticalAlignment="Stretch">
<Label x:Name="Player2Name" Style="{StaticResource PlayerName}">Player 2</Label>
<Label x:Name="Player2Score" Style="{StaticResource Score}">501</Label>
<ScrollViewer x:Name="Player2Throws" Height="380">
</ScrollViewer>
</StackPanel>
<StackPanel x:Name="Input" HorizontalAlignment="Center" VerticalAlignment="Top">
<TextBox x:Name="CurrentNumber" MaxLength="3" Width="160" Margin="20" FontSize="30" TextChanged="TextBox_TextChanged"></TextBox>
<Button x:Name="SubmitButton" IsDefault="True" Style="{StaticResource Golden} " Click="Button_Click">Запиши резултат</Button>
<Button x:Name="CancelButton" Style="{StaticResource Golden}">Върни назад</Button>
</StackPanel>
<Grid x:Name="StatsGrid" HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="60" Width="560">
<StackPanel x:Name="Player1Stats" HorizontalAlignment="Left" VerticalAlignment="Stretch">
<Label x:Name="Player1Legs" Style="{StaticResource BoardStats}"/>
<Label x:Name="Player1Sets" Style="{StaticResource BoardStats}"/>
<Label x:Name="Player1Avr" Style="{StaticResource BoardStats}"/>
</StackPanel>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Stretch">
<Label Style="{StaticResource BoardStats}">LEGS</Label>
<Label Style="{StaticResource BoardStats}">SETS</Label>
<Label Style="{StaticResource BoardStats}">3 DART AVERAGE</Label>
</StackPanel>
<StackPanel x:Name="Player2Stats" HorizontalAlignment="Right" VerticalAlignment="Stretch">
<Label x:Name="Player2Legs" Style="{StaticResource BoardStats}"/>
<Label x:Name="Player2Sets" Style="{StaticResource BoardStats}"/>
<Label x:Name="Player2Avr" Style="{StaticResource BoardStats}"/>
</StackPanel>
</Grid>
</Grid>
</Window>
App.xaml:
<Application x:Class="Scoreboard.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Scoreboard"
StartupUri="StartWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<Style x:Key="Golden" TargetType="Button">
<Setter Property="Background" Value="Gold" />
<Setter Property="BorderBrush" Value="Black" />
<Setter Property="Margin" Value="10" />
<Setter Property="Padding" Value="4" />
<Setter Property="FontSize" Value="30" />
</Style>
<Style x:Key="PlayerName" TargetType="Label">
<Setter Property="Background" Value="White" />
<Setter Property="Margin" Value="10"/>
<Setter Property="FontSize" Value="34" />
<Setter Property="BorderBrush" Value="Black" />
<Setter Property="BorderThickness" Value="2" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
</Style>
<Style x:Key="Score" TargetType="Label">
<Setter Property="Margin" Value="10" />
<Setter Property="Background" Value="White" />
<Setter Property="FontSize" Value="160" />
<Setter Property="BorderBrush" Value="Black" />
<Setter Property="BorderThickness" Value="2" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
</Style>
<Style x:Key="BoardStats" TargetType="Label">
<Setter Property="Background" Value="Beige" />
<Setter Property="Margin" Value="4" />
<Setter Property="BorderBrush" Value="DarkRed" />
<Setter Property="BorderThickness" Value="4" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="FontSize" Value="26" />
<Setter Property="Padding" Value="8" />
</Style>
</ResourceDictionary>
</Application.Resources>
</Application>
Heres the C# Code, It probably has more bugs that I know of, but it won't compile because it doesn't recognize any of the names from the XAML:
namespace Scoreboard
{
public partial class MainScoreBoard : Window
{
public MainScoreBoard()
{
InitializeComponent();
}
static bool IsTextAllowed(string text, int num)
{
Regex regex = new Regex("[^0-9.-]+");
if (!regex.IsMatch(text))
{
num = Int32.Parse(text);
return true;
}
else return false;
}
void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
}
void Button_Click(object sender, RoutedEventArgs e)
{
// You should get in the habit of initializing your variables
int currentScore = 0, currentPlayerScore, currentResult, legs, sets;
bool Player1Turn = true;
// You were getting null reference exceptions from attempting to access the Content property
// without them ever getting set. You can initialize them with default values in the xaml or in the
// constructor on initialization (After InitializeComponent() call).
if (Player1Score.Content != null && Player2Score.Content != null)
{
bool isGameOver = false;
// This will loop infinitely if you don't fix the bugs in calculating the score, I put a comment on it as you are not getting a value for currentScore from anywhere
while (Int32.Parse(Player1Score.Content.ToString()) != 0 || Int32.Parse(Player2Score.Content.ToString()) != 0)
{
if (IsTextAllowed(CurrentNumber.Text, currentScore))
{
if (currentScore > 180)
{
MessageBox.Show("Написаното е над 180!!!");
continue;
}
if (Player1Turn)
{
Player1Turn = false;
currentPlayerScore = Int32.Parse(Player1Score.Content.ToString());
// The currentScore variable is never getting set, I initialized it for you but
// it needs to be assigned a value from somewhere otherwise it will always be
// currentResult = currentPlayerScore - 0
currentResult = currentPlayerScore - currentScore;
if (currentResult < 0)
{
MessageBox.Show("Предобри!!!");
continue;
}
else if (currentResult == 0)
{
MessageBox.Show("Game Over!!!");
legs = Int32.Parse(Player1Legs.Content.ToString());
legs++;
Player1Legs.Content = legs.ToString();
if (legs == 3)
{
//increas sets
//if sets == 3, end game and shut down app
}
//Application.Current.Shutdown();
// Set flag so we do not keep looping through game over code
isGameOver = true;
}
Player1Score.Content = currentResult.ToString();
// Added this check here because I'm assuming you would want Player1Score to be reflected first
// before exiting the loop.
if (isGameOver)
{
// game is over, we do not want to keep showing the Game Over message box
break;
}
}
else
{
//the same for Player2
Player1Turn = true;
}
}
}
}
}
}
}
All of the controls have an x:Name on them, but I can access only 2 ot 3 of them, and even if I can, nothing happens. I want to write text in the textbox, and after some processing goes on with the textbox, some output and stats are supposed to show up on the labels, but it won't let me access any of it, or if it does, it's random, and nothing happens, as if there is no code there.
I cannot see the code for your Window declaration in the xaml. But since you renamed the namespace and class, you should have this in your xaml window declaration.
x:Class="Scoreboard.MainScoreBoard"
Essentially your xaml should look like this
<Window x:Class="Scoreboard.MainScoreBoard"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
</Window>
You do have a lot more bugs in the code behind as you need to call .ToString() in several places as well as some null reference exceptions, but if you get stuck in there you can always post a new question.
public partial class MainScoreBoard : Window
{
public MainScoreBoard()
{
InitializeComponent();
}
static bool IsTextAllowed(string text, int num)
{
Regex regex = new Regex("[^0-9.-]+");
if (!regex.IsMatch(text))
{
num = Int32.Parse(text);
return true;
}
else return false;
}
void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
}
void Button_Click(object sender, RoutedEventArgs e)
{
// You should get in the habit of initializing your variables
int currentScore = 0, currentPlayerScore, currentResult, legs, sets;
bool Player1Turn = true;
// You were getting null reference exceptions from attempting to access the Content property
// without them ever getting set. You can initialize them with default values in the xaml or in the
// constructor on initialization (After InitializeComponent() call).
if (Player1Score.Content != null && Player2Score.Content != null)
{
bool isGameOver = false;
// This will loop infinitely if you don't fix the bugs in calculating the score, I put a comment on it as you are not getting a value for currentScore from anywhere
while (Int32.Parse(Player1Score.Content.ToString()) != 0 || Int32.Parse(Player2Score.Content.ToString()) != 0)
{
if (IsTextAllowed(CurrentNumber.Text, currentScore))
{
if (currentScore > 180)
{
MessageBox.Show("Написаното е над 180!!!");
continue;
}
if (Player1Turn)
{
Player1Turn = false;
currentPlayerScore = Int32.Parse(Player1Score.Content.ToString());
// The currentScore variable is never getting set, I initialized it for you but
// it needs to be assigned a value from somewhere otherwise it will always be
// currentResult = currentPlayerScore - 0
currentResult = currentPlayerScore - currentScore;
if (currentResult < 0)
{
MessageBox.Show("Предобри!!!");
continue;
}
else if (currentResult == 0)
{
MessageBox.Show("Game Over!!!");
legs = Int32.Parse(Player1Legs.Content.ToString());
legs++;
Player1Legs.Content = legs.ToString();
if (legs == 3)
{
//increas sets
//if sets == 3, end game and shut down app
}
//Application.Current.Shutdown();
// Set flag so we do not keep looping through game over code
isGameOver = true;
}
Player1Score.Content = currentResult.ToString();
// Added this check here because I'm assuming you would want Player1Score to be reflected first
// before exiting the loop.
if (isGameOver)
{
// game is over, we do not want to keep showing the Game Over message box
break;
}
}
else
{
//the same for Player2
Player1Turn = true;
}
}
}
}
}
}
Those styles in the xaml are not defined anywhere you should remove them until you implement the styles. I removed them when compiling as they are not defined.
<Window x:Class="Scoreboard.MainScoreBoard"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Scoreboard"
Title="DartsLeague" Height="720" Width="1280">
<Grid x:Name="MainGrid" Background="DarkGreen">
<StackPanel x:Name="Player1" HorizontalAlignment="Left" VerticalAlignment="Stretch">
<Label x:Name="Player1Name"/>
<Label x:Name="Player1Score">501</Label>
<ScrollViewer x:Name="Player1Throws" Height="380" FlowDirection="RightToLeft">
</ScrollViewer>
</StackPanel>
<StackPanel x:Name="Player2" HorizontalAlignment="Right" VerticalAlignment="Stretch">
<Label x:Name="Player2Name">Player 2</Label>
<Label x:Name="Player2Score">501</Label>
<ScrollViewer x:Name="Player2Throws" Height="380">
</ScrollViewer>
</StackPanel>
<StackPanel x:Name="Input" HorizontalAlignment="Center" VerticalAlignment="Top">
<TextBox x:Name="CurrentNumber" MaxLength="3" Width="160" Margin="20" FontSize="30" TextChanged="TextBox_TextChanged"></TextBox>
<Button x:Name="SubmitButton" IsDefault="True" Click="Button_Click">Запиши резултат</Button>
<Button x:Name="CancelButton">Върни назад</Button>
</StackPanel>
<Grid x:Name="StatsGrid" HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="60" Width="560">
<StackPanel x:Name="Player1Stats" HorizontalAlignment="Left" VerticalAlignment="Stretch">
<Label x:Name="Player1Legs">0</Label>
<Label x:Name="Player1Sets" />
<Label x:Name="Player1Avr" />
</StackPanel>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Stretch">
<Label>LEGS</Label>
<Label>SETS</Label>
<Label>3 DART AVERAGE</Label>
</StackPanel>
<StackPanel x:Name="Player2Stats" HorizontalAlignment="Right" VerticalAlignment="Stretch">
<Label x:Name="Player2Legs"/>
<Label x:Name="Player2Sets"/>
<Label x:Name="Player2Avr" />
</StackPanel>
</Grid>
</Grid>
Related
I have a WPF application where I have view which lets User choose list of fields at run-time and I am storing in a Text file and I am trying to create a Data entry form based on the list of fields that user created Run-time.
I have developed an solution using code behind but I am trying to implement this using MVVM.
Approach 1: I can create the textBlock and Textbox in the code-behind and bind it to the properties in the Viewmodel. The viewmodel will have all the possible field property.
<TabControl Margin="335,10,10,71" TabStripPlacement="Bottom">
<TabControl.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid>
<Border
Name="Border"
Margin="0,0,0,0"
Background="Transparent"
BorderBrush="Black"
BorderThickness="1,1,1,1"
CornerRadius="5">
<ContentPresenter
x:Name="ContentSite"
Margin="12,2,12,2"
HorizontalAlignment="Center"
VerticalAlignment="Center"
ContentSource="Header"
RecognizesAccessKey="True">
<ContentPresenter.LayoutTransform>
<RotateTransform Angle="0" />
</ContentPresenter.LayoutTransform>
</ContentPresenter>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Height" Value="40" />
<Setter Property="FontSize" Value="20" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Panel.ZIndex" Value="200" />
<Setter TargetName="Border" Property="Background" Value="Black" />
<Setter TargetName="Border" Property="BorderBrush" Value="White" />
<Setter TargetName="Border" Property="BorderThickness" Value="1,1,1,0" />
<Setter Property="Foreground" Value="White" />
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="FontSize" Value="20" />
<Setter Property="FontWeight" Value="Bold" />
<Setter TargetName="Border" Property="Background" Value="Black" />
<Setter TargetName="Border" Property="BorderBrush" Value="White" />
<Setter Property="Foreground" Value="White" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.Resources>
<TabItem Header="Product Data 1">
<Grid x:Name="Grid1">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="Column_1" Width="*" />
<ColumnDefinition x:Name="Column_2" Width="*" />
</Grid.ColumnDefinitions>
<Border
Margin="0,0,0,10"
Background="LightSkyBlue"
BorderBrush="Gray"
BorderThickness="2,2,2,2">
<StackPanel
x:Name="MainStack"
Margin="20,0,0,0"
x:FieldModifier="public"
Grid.IsSharedSizeScope="True"
Orientation="Vertical" />
</Border>
<Border
Grid.Column="1"
Margin="0,0,0,10"
Background="LightSkyBlue"
BorderBrush="Gray"
BorderThickness="2,2,2,2">
<StackPanel
x:Name="SecondStack"
Grid.Column="1"
Margin="20,0,0,0"
x:FieldModifier="public"
Grid.IsSharedSizeScope="True"
Orientation="Vertical" />
</Border>
<!--<StackPanel x:Name="ThirdStack" x:FieldModifier="public" Grid.Column="2" Orientation="Vertical" Grid.IsSharedSizeScope="True" Width="auto" Height="476" VerticalAlignment="Center" HorizontalAlignment="Center" />
<Border BorderBrush="Black" BorderThickness="1,1,1,1" Grid.Column="2" Margin="0,0,0,10"/>-->
</Grid>
</TabItem>
</TabControl>
Code-Behind=
string[] Data = File.ReadAllLines("Field.txt");
foreach (var part in Data)
{
Grid mytxtBStack = new Grid();
MainStack.Children.Add(mytxtBStack);
ColumnDefinition column_1 = new ColumnDefinition();
column_1.SharedSizeGroup = "FirstColumn";
ColumnDefinition column_2 = new ColumnDefinition();
column_2.SharedSizeGroup = "SecondColumn";
mytxtBStack.ColumnDefinitions.Add(column_1);
mytxtBStack.ColumnDefinitions.Add(column_2);
txtBlock = new TextBlock();
txtBlock.Name = "MyBlock" + i;
txtBlock.Text = part;
txtBlock.FontSize = 14;
txtBlock.FontWeight = FontWeights.DemiBold;
txtBlock.Foreground = Brushes.Black;
txtBlock.Margin = new Thickness(0, 20, 0, 0);
mytxtBStack.Children.Add(txtBlock);
txtBox = new TextBox();
txtBox.Name = "MyText" + i.ToString();
txtBox.FontSize = 12;
txtBox.Height = 25;
txtBox.Width = 250;
txtBox.BorderThickness = new Thickness(1, 1, 1, 1);
txtBox.Margin = new Thickness(130, 20, 0, 0);
txtBox.Foreground = Brushes.Black;
txtBox.BorderBrush = Brushes.Black;
txtBox.Background = Brushes.White;
txtList.Add(txtBox);
mytxtBStack.Children.Add(txtBox);
}
Approach 2: I can create a dynamic class using the Expandoobject based on the Textfile and create a itemscontrols to bind with view and mentioned all the possibile field properties in the Viewmodel.
I am thinking about implementing this part with
inside the TabControl,
<ItemsControl Grid.Row="1" ItemsSource="{Binding Path=Fields">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" />
<TextBox Width="300" Text="{Binding}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Can anyone please suggest me a way to do this with MVVM wpf?
I implemented this using the CommunityToolkit.Mvvm NuGet package.
MainWindowViewModel.cs
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Collections.ObjectModel;
namespace WpfApp1;
public class Field
{
public string Block { get; set; } = string.Empty;
public string Text { get; set; } = string.Empty;
}
public partial class MainWindowViewModel : ObservableObject
{
[ObservableProperty]
// CommunityToolkit's source generator will create a "Field" property.
private ObservableCollection<Field> fields = new();
[RelayCommand]
// CommunityToolkit's source generator will create a "LoadFieldsCommand" command.
private void LoadFields()
{
for (int i = 0; i < 10; i++)
{
Fields.Add(new Field() { Block = $"Block#{i + 1}", Text = $"Text{i + 1}" });
}
}
[ObservableProperty]
// Bind this to the Text property.
private string textBlockText = string.Empty;
[ObservableProperty]
// Bind this to the ItemsSource property.
private List<string> comboBoxItems = new()
{
"Item A",
"Item B",
"Item C",
};
}
MainWindow.xaml.cs
using System.Windows;
namespace WpfApp1;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public MainWindowViewModel ViewModel { get; } = new();
}
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:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApp1"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Name="ThisWindow"
Title="MainWindow"
Width="800"
Height="450"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button
Grid.Row="0"
Command="{Binding ElementName=ThisWindow, Path=ViewModel.LoadFieldsCommand}"
Content="Load fields" />
<ItemsControl Grid.Row="1" ItemsSource="{Binding ElementName=ThisWindow, Path=ViewModel.Fields}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="local:Field">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Block}" />
<TextBox Text="{Binding Text}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
I'm making an application using C# and WPF
I have 8 toggle buttons.
When I click one of the buttons others should be disabled so I can't click it except one is activated.
When I click this button again others should be enabled
Styles.xaml:
<!--Toggle Button-->
<Style x:Key="ToggleButton" TargetType="{x:Type ToggleButton}">
<Setter Property="Background" Value="#535353"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="MinHeight" Value="30"/>
<Setter Property="Grid.Column" Value="1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Background="{TemplateBinding Background}"
BorderThickness="0">
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
MainWindow.xaml:
<ToggleButton x:Name="CategoryToggle1"
Grid.Row="1"
Style="{StaticResource ToggleButton}"
Checked="ShowOptions"
Unchecked="HideOptions" />
How Can I achieve this by code and XAML?
I want to do it like in this video:
Video
Thank you for comments guys, I found another solution.
MainWindow.XAML:
<RadioButton x:Name="CategoryToggle1"
Grid.Row="1"
Grid.Column="1"
Style="{StaticResource RadioButton}"
GroupName="ToggleButtonsGroup"
Checked="OpenOptions"
Unchecked="HideOptions"/>
Styles.xaml:
<!--Radio Button-->
<Style TargetType="RadioButton"
x:Key="RadioButton"
BasedOn="{StaticResource {x:Type ToggleButton}}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<ToggleButton IsChecked="{Binding IsChecked, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!--Toggle Button-->
<Style TargetType="{x:Type ToggleButton}">
<Setter Property="Background" Value="#535353" />
<Setter Property="MinHeight" Value="30" />
<Setter Property="Grid.Column" Value="1" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Border Background="{TemplateBinding Background}"
BorderThickness="0">
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Opacity" Value="0.5" />
</Trigger>
<Trigger Property="IsEnabled" Value="true">
<Setter Property="Opacity" Value="1" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
MainWindow.cs:
private void OpenOptions(object sender, RoutedEventArgs e){
RadioButton radioButton = sender as RadioButton;
radioButton.IsChecked = true;
//Disable all option buttons except one that active
MyGrid.Children.OfType<RadioButton>().Where(rb => rb != radioButton &&
rb.GroupName == radioButton.GroupName).ToList().ForEach(rb => rb.IsEnabled = false);
}
private void HideOptions(object sender, RoutedEventArgs e)
{
RadioButton radioButton = sender as RadioButton;
MyGrid.Children.OfType<RadioButton>().Where(rb => rb.GroupName ==
radioButton.GroupName).ToList().ForEach(rb => rb.IsEnabled = true);
}
Using Click events of each ToggleButton
One way you could do it is by giving a name to all your ToggleButtons, hook-up to their Click event and manually uncheck others in the code-behind:
XAML
<StackPanel Orientation="Vertical">
<StackPanel.Resources>
<Style TargetType="ToggleButton">
<Style.Triggers>
<Trigger Property="IsChecked" Value="False">
<Setter Property="MaxWidth" Value="15"/>
</Trigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
<StackPanel Orientation="Horizontal">
<ToggleButton x:Name="button1" Content="Button 1" Click="button1_Click"/>
<TextBlock Text="Line 1"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<ToggleButton x:Name="button2" Content="Button 2" Click="button2_Click"/>
<TextBlock Text="Line 2"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<ToggleButton x:Name="button3" Content="Button 3" Click="button3_Click"/>
<TextBlock Text="Line 3"/>
</StackPanel>
</StackPanel>
Code-behind
private void button1_Click(object sender, RoutedEventArgs e) {
if (button1.IsChecked == true) {
button2.IsChecked = false;
button3.IsChecked = false;
}
}
private void button2_Click(object sender, RoutedEventArgs e) {
if (button2.IsChecked == true) {
button1.IsChecked = false;
button3.IsChecked = false;
}
}
private void button3_Click(object sender, RoutedEventArgs e) {
if (button3.IsChecked == true) {
button1.IsChecked = false;
button2.IsChecked = false;
}
}
This method is tedious, error-prone, requires code-behind and is not very scalable.
Binding IsChecked properties to a collection of bool with one true at a time.
Another way you could go (still by using code-behind) is to define a collection of boolean values and bind each ToggleButton.IsChecked on one of the bool in the collection, and ensure that the collection only contains at most one true at a time:
<StackPanel Orientation="Horizontal">
<ToggleButton x:Name="button1" Content="Button 1" IsChecked="{Binding [0]}"/>
<TextBlock Text="Line 1"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<ToggleButton x:Name="button2" Content="Button 2" IsChecked="{Binding [1]}"/>
<TextBlock Text="Line 2"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<ToggleButton x:Name="button3" Content="Button 3" IsChecked="{Binding [2]}"/>
<TextBlock Text="Line 3"/>
</StackPanel>
Code-behind
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
ObservableCollection<bool> states = new ObservableCollection<bool> { false, false, false };
states.CollectionChanged += States_CollectionChanged;
DataContext = states;
}
private void States_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
var collection = sender as ObservableCollection<bool>;
if (e.Action == NotifyCollectionChangedAction.Replace) {
if ((bool)e.NewItems[0]) {
for (int i = 0; i < collection.Count; i++) {
if (e.NewStartingIndex != i) {
collection[i] = false;
}
}
}
}
}
}
Again, this uses code-behind and not the view model but at least it is easier to add rows.
The behavior you need is very specific.
I don't know how, but i got here trying to make a toggle button behave like a radio button. Your answer was enlightning.
For what it's worth, here's how you would do that :
Resource :
<Style x:Key='RadioToggle' TargetType='RadioButton'
BasedOn='{StaticResource {x:Type ToggleButton}}' />
Control :
<RadioButton Content='RadioToggle1' IsChecked='True'
Style='{StaticResource RadioToggle}'
GroupName="RadioToggleGroup" />
<RadioButton Content='RadioToggle2'
Style='{StaticResource RadioToggle}'
GroupName="RadioToggleGroup" />
<RadioButton Content='RadioToggle3'
Style='{StaticResource RadioToggle}'
GroupName="RadioToggleGroup" />
As topic mentioned.I want to use only one popup for all button in my application.I don't know how to get what I want.
Here is what my window looks like:
Info 1:
Info 2:
You can see popup appear on wrong position.I know I can position a popup by setting the PlacementTarget.But each Popup has a different value for the placement property.That is the problem.I'm looking another way to do it.
Here is a popup for option 1:
<StackPanel Orientation="Horizontal">
<!--Option 1: text and button-->
<TextBlock Text="Option 1"
Margin="10"
VerticalAlignment="Center" />
<Popup x:Name="popInfo"
PlacementTarget="{Binding ElementName=btnInfoOption1}"
IsOpen="{Binding IsShowInfo1}">
<ContentControl Style="{StaticResource ContentInfoStyle}">
<TextBlock Text="{Binding InfoContent}"
TextWrapping="Wrap"
Foreground="White"
Width="340"
Padding="10"
Margin="30,0,30,5"
FontSize="15" />
</ContentControl>
</Popup>
<Button x:Name="btnInfoOption1"
Style="{StaticResource btnIcons}"
Background="#0063b1"
Width="30"
Height="30"
Margin="10,10,20,10"
Command="{Binding CmdShowInfo, Delay=1500}"
Tag="{StaticResource ic_ginfo}" />
</StackPanel>
popup for option 2:
<StackPanel Orientation="Horizontal">
<!--Option 2: text and button-->
<TextBlock Text="Option 2"
Margin="10"
VerticalAlignment="Center" />
<Button x:Name="btnOption2"
Style="{StaticResource btnIcons}"
Background="#0063b1"
Width="30"
Height="30"
Margin="10,10,20,10"
Command="{Binding CmdShowInfo, Delay=1500}"
Tag="{StaticResource ic_ginfo}" />
</StackPanel>
ContentControl Style:
<Style TargetType="{x:Type ContentControl}"
x:Key="ContentInfoStyle">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Border Background="Green"
CornerRadius="3"
Padding="10,0,12,10">
<StackPanel>
<Button HorizontalAlignment="Right"
Tag="{StaticResource ic_gclear}"
Style="{StaticResource btnIcons}"
Background="White"
Margin="10,5,12,5"
Command="{Binding DataContext.CmdCloseInfo}"
Height="24" />
<ContentPresenter x:Name="content"
TextBlock.FontSize="14"
TextBlock.Foreground="White"
TextBlock.FontFamily="Arial"
Content="{TemplateBinding ContentControl.Content}" />
</StackPanel>
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
Button icon style:
<Style TargetType="Button"
x:Key="btnIcons">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="brd" Background="Transparent"
SnapsToDevicePixels="True">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="MouseOver" />
<VisualState x:Name="Pressed" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Path Stretch="Uniform" VerticalAlignment="Center"
Fill="{TemplateBinding Background}"
Data="{TemplateBinding Tag}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
ViewModel.cs:
the content of popup:
private string _InfoContent;
public string InfoContent
{
get { return _InfoContent; }
set
{
if (value != _InfoContent)
{
_InfoContent = value;
OnRaise("InfoContent");
}
}
}
show the popup for option2 and option1:
private bool _IsShowInfo2;
public bool IsShowInfo2
{
get { return _IsShowInfo2; }
set
{
if (value != _IsShowInfo2)
{
_IsShowInfo2 = value;
OnRaise("IsShowInfo2");
}
}
}
//show the popup for option1
private bool _IsShowInfo1;
public bool IsShowInfo1
{
get { return _IsShowInfo1; }
set
{
if (value != _IsShowInfo1)
{
_IsShowInfo1 = value;
OnRaise("IsShowInfo1");
}
}
}
the command for button:
private ICommand _CmdShowInfo;
public ICommand CmdShowInfo
{
get
{
_CmdShowInfo = _CmdShowInfo ?? new RelayCommand(x => this.ShowInfo(true, 1), () => true);
return _CmdShowInfo;
}
}
private ICommand _CmdShowInfo2;
public ICommand CmdShowInfo2
{
get
{
_CmdShowInfo2 = _CmdShowInfo2 ?? new RelayCommand(x => this.ShowInfo(true, 0), () => true);
return _CmdShowInfo2;
}
}
private void ShowInfo(bool show = true, byte option = 0)
{
if (option == 0)
{
this.InfoContent = "Option 1...";
}
else if (option == 1)
{
this.InfoContent = "Option 2...";
}
this.IsShowInfo1 = show;
}
My initial thought was to do this with a styled HeaderedContentControl, but then you've got the icon fill color and the icon data, and I'd have had to add attached properties for those. Once you go there, you may as well just write a custom control.
The dependency properties of IconPopupButton can be bound like any dependency property:
<hec:IconPopupButton
IsOpen="{Binding IsShowInfo1}"
IconFill="YellowGreen"
Content="Another Test Popup"
IconData="M -10,-10 M 0,3 L 17,20 L 20,17 L 3,0 Z M 0,0 L 0,20 L 20,20 L 20,0 Z"
/>
If you want to parameterize the Style applied to the ContentControl in the Popup, add another dependency property. You'll need to give that some thought, though, because you need that ToggleButton to be bound to IsOpen on the templated parent, one way or another. Perhaps you could bind it to the viewmodel property that's bound to the popup button's IsOpen. There's always a way.
So here's that. With snippets to create dependency properties, this is pretty much just a fill-in-the-blanks exercise. Much less to it than meets the eye.
IconPopupButton.cs
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
namespace HollowEarth.Controls
{
public class IconPopupButton : ContentControl
{
static IconPopupButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(IconPopupButton), new FrameworkPropertyMetadata(typeof(IconPopupButton)));
}
#region IconData Property
public Geometry IconData
{
get { return (Geometry)GetValue(IconDataProperty); }
set { SetValue(IconDataProperty, value); }
}
public static readonly DependencyProperty IconDataProperty =
DependencyProperty.Register("IconData", typeof(Geometry), typeof(IconPopupButton),
new PropertyMetadata(null));
#endregion IconData Property
#region IconFill Property
public Brush IconFill
{
get { return (Brush)GetValue(IconFillProperty); }
set { SetValue(IconFillProperty, value); }
}
public static readonly DependencyProperty IconFillProperty =
DependencyProperty.Register("IconFill", typeof(Brush), typeof(IconPopupButton),
new PropertyMetadata(SystemColors.ControlTextBrush));
#endregion IconFill Property
#region IsOpen Property
public bool IsOpen
{
get { return (bool)GetValue(IsOpenProperty); }
set { SetValue(IsOpenProperty, value); }
}
public static readonly DependencyProperty IsOpenProperty =
DependencyProperty.Register("IsOpen", typeof(bool), typeof(IconPopupButton),
new PropertyMetadata(false));
#endregion IsOpen Property
#region StaysOpen Property
public bool StaysOpen
{
get { return (bool)GetValue(StaysOpenProperty); }
set { SetValue(StaysOpenProperty, value); }
}
public static readonly DependencyProperty StaysOpenProperty =
DependencyProperty.Register("StaysOpen", typeof(bool), typeof(IconPopupButton),
new PropertyMetadata(false));
#endregion StaysOpen Property
#region Placement Property
public PlacementMode Placement
{
get { return (PlacementMode)GetValue(PlacementProperty); }
set { SetValue(PlacementProperty, value); }
}
public static readonly DependencyProperty PlacementProperty =
DependencyProperty.Register("Placement", typeof(PlacementMode), typeof(IconPopupButton),
new PropertyMetadata(PlacementMode.Right));
#endregion Placement Property
}
}
Themes\Shared.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:HeaderedPopupTest.Themes"
>
<Geometry x:Key="ic_gclear">M56,4 52,0 28,24 4,0 0,4 24,28 0,52 4,56 28,32 52,56 56,52 32,28Z</Geometry>
<Geometry x:Key="ic_ginfo">M31,0C13.879,0,0,13.879,0,31s13.879,31,31,31s31-13.879,31-31S48.121,0,31,0z M34,46h-6V27.969h6V46z M34,21.969h-6V16h6V21.969z</Geometry>
<Style TargetType="ButtonBase" x:Key="btnIcons">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ButtonBase}">
<Border x:Name="brd" Background="Transparent" SnapsToDevicePixels="True">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="MouseOver" />
<VisualState x:Name="Pressed" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid>
<Path
x:Name="Path"
Stretch="Uniform"
VerticalAlignment="Center"
Fill="{TemplateBinding Background}"
Data="{TemplateBinding Tag}"
/>
<TextBlock
x:Name="MissingIconData"
Visibility="Collapsed"
Text="?"
FontWeight="Bold"
FontSize="30"
ToolTip="IconData (Tag) not set"
HorizontalAlignment="Center"
VerticalAlignment="Center"
/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="Tag" Value="{x:Null}">
<Setter TargetName="MissingIconData" Property="Visibility" Value="Visible" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Themes\Generic.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:HeaderedPopupTest.Themes"
xmlns:hec="clr-namespace:HollowEarth.Controls"
>
<ResourceDictionary.MergedDictionaries>
<!-- Change HeaderedPopupTest to the name of your own assembly -->
<ResourceDictionary Source="/HeaderedPopupTest;component/Themes/Shared.xaml" />
</ResourceDictionary.MergedDictionaries>
<Style TargetType="hec:IconPopupButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="hec:IconPopupButton">
<Grid x:Name="Grid">
<ToggleButton
x:Name="OpenButton"
Style="{StaticResource btnIcons}"
Background="{TemplateBinding IconFill}"
Tag="{TemplateBinding IconData}"
IsChecked="{Binding IsOpen, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
ToolTip="{TemplateBinding ToolTip}"
/>
<Popup
x:Name="Popup"
StaysOpen="{Binding StaysOpen, RelativeSource={RelativeSource TemplatedParent}}"
IsOpen="{Binding IsOpen, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
PlacementTarget="{Binding ElementName=ToggleButton}"
Placement="{TemplateBinding Placement}"
>
<Border
Background="Green"
CornerRadius="3"
Padding="10,0,12,10">
<StackPanel>
<ToggleButton
HorizontalAlignment="Right"
Tag="{StaticResource ic_gclear}"
Style="{StaticResource btnIcons}"
Background="White"
Margin="10,5,12,5"
IsChecked="{Binding IsOpen, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
Height="24"
/>
<ContentPresenter
x:Name="content"
TextBlock.FontSize="14"
TextBlock.Foreground="White"
TextBlock.FontFamily="Arial"
Content="{TemplateBinding Content}"
/>
</StackPanel>
</Border>
</Popup>
</Grid>
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<!--
I don't understand this: If I use the templated parent's IsOpen,
the effect is as if it were never true.
-->
<Condition SourceName="Popup" Property="IsOpen" Value="True" />
<Condition Property="StaysOpen" Value="False" />
</MultiTrigger.Conditions>
<!--
If StaysOpen is false and the button is enabled while the popup is open,
then clicking on it will cause the popup to flicker rather than close.
-->
<Setter TargetName="OpenButton" Property="IsEnabled" Value="False" />
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
MainWindow.xaml example usage:
<Window
x:Class="HeaderedPopupTest.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:HeaderedPopupTest"
xmlns:hec="clr-namespace:HollowEarth.Controls"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525"
>
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Themes\Shared.xaml" />
</ResourceDictionary.MergedDictionaries>
<Style
x:Key="InfoPopupButton"
TargetType="hec:IconPopupButton"
BasedOn="{StaticResource {x:Type hec:IconPopupButton}}"
>
<Setter Property="IconFill" Value="DeepSkyBlue" />
<Setter Property="IconData" Value="{StaticResource ic_ginfo}" />
</Style>
</ResourceDictionary>
</Window.Resources>
<Grid>
<StackPanel
Orientation="Vertical"
HorizontalAlignment="Left"
>
<hec:IconPopupButton
Style="{StaticResource InfoPopupButton}"
Content="This is a test popup"
ToolTip="Test Popup Tooltip"
/>
<hec:IconPopupButton
IconFill="YellowGreen"
Content="Another Test Popup"
IconData="M -10,-10 M 0,3 L 17,20 L 20,17 L 3,0 Z M 0,0 L 0,20 L 20,20 L 20,0 Z"
/>
<hec:IconPopupButton
IconFill="DarkRed"
Content="Missing IconData behavior example"
/>
</StackPanel>
</Grid>
</Window>
You'll notice I changed your buttons to ToggleButton. This is for convenience in wiring them up to the IsOpen property: With a ToggleButton, I just bind IsChecked and I'm done. No need for commands. One side effect of that is that if StaysOpen is false, then when the user clicks on the open button for a Popup, the focus change closes the Popup, which unchecks the button, and then the button gets the mouse message. So the button opens the popup again. This is bizarre behavior from the user's perspective, so you add a trigger to disable the button when the popup is open and StaysOpen is false. When StaysOpen is true, focus change doesn't close the Popup, so you want the button to be enabled in that case.
I changed the btnIcons style to target ButtonBase, so it works identically with Button and ToggleButton.
I'm new in WPF and I need to make a button (X) to simulate the X button in the form. First I set WindowStyle="None". Then make a Rectangle:
<Rectangle Height="16" HorizontalAlignment="Left" Margin="482,4,0,0" Name="x_btn" Stroke="#00000000" VerticalAlignment="Top" Width="17" MouseEnter="x_btn_MouseEnter" MouseLeftButtonUp="x_btn_MouseLeftButtonUp">
<Rectangle.Fill>
<ImageBrush ImageSource="/Red%20Crescent;component/Images/x_btn.png" />
</Rectangle.Fill>
</Rectangle>
After this I want to change the Background Image OnMousEnter event behind code:
private void x_btn_MouseEnter(object sender, MouseEventArgs e)
{
}
private void x_btn_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
this.Close();
}
Please help as fast as you can.
Note: I tried to use a Button but it's leave a border and i don't want that.
In your case it is better to use the Style for the Button, and use the Path instead of the Image. To properly implement the function of closing, it is better to implement it through a DependencyProperty and set the value directly in Style.
Example:
<Window x:Class="CloseButtonHelp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CloseButtonHelp"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<!-- Our style for the ToggleButton -->
<Style x:Key="ToggleButtonWindowClose" TargetType="{x:Type ToggleButton}">
<!-- Here you can set the initial properties for the control -->
<Setter Property="Background" Value="Transparent" />
<Setter Property="SnapsToDevicePixels" Value="True" />
<!-- Template needs to completely re-writing the standard control -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Grid>
<!-- Then responsible for the content. In our case it did not really need, because it is set Path -->
<ContentPresenter x:Name="MyContentPresenter" Content="{TemplateBinding Content}" HorizontalAlignment="Center" VerticalAlignment="Center" />
<!-- Our Path. Shows a cross -->
<Path x:Name="CloseWindow" SnapsToDevicePixels="True" ToolTip="Close window" Width="18" Height="17" Margin="0,0,10,0" HorizontalAlignment="Right" VerticalAlignment="Center" Stretch="Fill" Fill="#2D2D2D" Data="F1 M 26.9166,22.1667L 37.9999,33.25L 49.0832,22.1668L 53.8332,26.9168L 42.7499,38L 53.8332,49.0834L 49.0833,53.8334L 37.9999,42.75L 26.9166,53.8334L 22.1666,49.0833L 33.25,38L 22.1667,26.9167L 26.9166,22.1667 Z " />
</Grid>
<!-- Trigger fires on the property -->
<ControlTemplate.Triggers>
<!-- Here change the color when the mouse cursor -->
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="CloseWindow" Property="Fill" Value="#C10000" />
</Trigger>
<!-- Use ToggleButton, because it has a property IsChecked, accessible through the style -->
<Trigger Property="IsChecked" Value="True">
<Setter Property="local:WindowBehaviours.Close" Value="True" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<!-- Set the our style by key --->
<ToggleButton Name="CloseButton" Style="{StaticResource ToggleButtonWindowClose}" />
</Grid>
</Window>
Listing of WindowBehaviorsClass:
public static class WindowBehaviours
{
// Closing window
public static void SetClose(DependencyObject target, bool value)
{
target.SetValue(CloseProperty, value);
}
public static readonly DependencyProperty CloseProperty =
DependencyProperty.RegisterAttached("Close",
typeof(bool),
typeof(WindowBehaviours),
new UIPropertyMetadata(false, OnClose));
private static void OnClose(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is bool && ((bool)e.NewValue))
{
Window window = GetWindow(sender);
if (window != null)
{
window.Close();
}
}
}
private static Window GetWindow(DependencyObject sender)
{
Window window = null;
if (sender is Window)
{
window = (Window)sender;
}
if (window == null)
{
window = Window.GetWindow(sender);
}
return window;
}
}
For more information see MSDN.
When using WindowChrome (downloadable here) to customize the nonclient area of a window, a natural starting point is to make a title bar that looks and acts identical to a standard title bar. This requires adding a "fake" application icon and title bar, because apparently WindowChrome disables those features (the minimize, maximize and close buttons still work.)
Here's what I have so far:
<Window x:Class="MyApp.MainWindow"
x:Name="MainWindowItself"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:shell="clr-namespace:Microsoft.Windows.Shell;assembly=Microsoft.Windows.Shell"
xmlns:local="clr-namespace:MyApp"
Title="My Application" Icon="App.ico" Height="350" Width="525">
<Window.Resources>
<Style TargetType="{x:Type local:MainWindow}">
<Setter Property="shell:WindowChrome.WindowChrome">
<Setter.Value>
<shell:WindowChrome />
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MainWindow}">
<Grid>
<Border Background="White" Margin="{Binding Source={x:Static shell:SystemParameters2.Current}, Path=WindowNonClientFrameThickness}">
<ContentPresenter Content="{TemplateBinding Content}" />
</Border>
<TextBlock Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Title}"
VerticalAlignment="Top" HorizontalAlignment="Left"
Margin="32,8,0,0"/>
<Image x:Name="SystemMenuIcon" Source="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Icon}"
VerticalAlignment="Top" HorizontalAlignment="Left"
Margin="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(shell:WindowChrome.WindowChrome).ResizeBorderThickness}"
Width="{Binding Source={x:Static shell:SystemParameters2.Current}, Path=SmallIconSize.Width}"
shell:WindowChrome.IsHitTestVisibleInChrome="True" MouseDown="SystemMenuIcon_MouseDown">
</Image>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<TextBlock Text="Client area content goes here"/>
</Grid>
</Window>
Code behind:
private void SystemMenuIcon_MouseDown(object sender, MouseButtonEventArgs e)
{
var offs = SystemParameters2.Current.WindowNonClientFrameThickness;
SystemCommands.ShowSystemMenu(this, new Point(Left + offs.Left, Top + offs.Top));
}
This comes very close to working. The first problem is that after you click the application icon and the system menu appears, the menu should disappear if you click a second time--instead, the menu just redraws. Also, if you double-click then the window should close, but Image doesn't have a double-click event. How would you suggest adding these features?
To disable working of standard Chrome Buttons Just add an extra attribute CaptionHeight="0" in your XAML code of shell:WindowsChrome
So it will be like that
<Setter Property="shell:WindowChrome.WindowChrome">
<Setter.Value>
<shell:WindowChrome CaptionHeight="0" />
</Setter.Value>
</Setter>
To make a fake Chrome. Modify the Template to like this:
<ControlTemplate TargetType="Window">
<AdornerDecorator>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="35" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid x:Name="PART_Chrome" shell:WindowChrome.IsHitTestVisibleInChrome="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="105" />
</Grid.ColumnDefinitions>
<Image Source="Application Favicon Path" />
<TextBlock Grid.Column="1" Text="{TemplateBinding Title}" VerticalAlignment="Center" />
<StackPanel Orientation="Horizontal" Grid.Column="3" >
<Button Command="{Binding Source={x:Static shell:SystemCommands.MinimizeWindowCommand}}" >
<Path Data="M0,6 L8,6 Z" Width="8" Height="7" VerticalAlignment="Center" HorizontalAlignment="Center"
Stroke="{Binding Foreground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Button}}" StrokeThickness="2" />
</Button>
<Button x:Name="MaximizeButton" Command="{Binding Source={x:Static shell:SystemCommands.MaximizeWindowCommand}}" >
<Path Data="M0,1 L9,1 L9,8 L0,8 Z" Width="9" Height="8" VerticalAlignment="Center" HorizontalAlignment="Center"
Stroke="{Binding Foreground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Button}}" StrokeThickness="2" />
</Button>
<Button x:Name="RestoreButton" Command="{Binding Source={x:Static shell:SystemCommands.RestoreWindowCommand}}" >
<Path Data="M2,0 L8,0 L8,6 M0,3 L6,3 M0,2 L6,2 L6,8 L0,8 Z" Width="8" Height="8" VerticalAlignment="Center" HorizontalAlignment="Center"
Stroke="{Binding Foreground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Button}}" StrokeThickness="1" />
</Button>
<Button Command="{Binding Source={x:Static shell:SystemCommands.CloseWindowCommand}}" >
<Path Data="M0,0 L8,7 M8,0 L0,7 Z" Width="8" Height="7" VerticalAlignment="Center" HorizontalAlignment="Center"
Stroke="{Binding Foreground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Button}}" StrokeThickness="1.5" />
</Button>
</StackPanel>
</Grid>
<ContentPresenter Grid.Row="1" Content="{TemplateBinding Content}" />
</Grid>
</AdornerDecorator>
<ControlTemplate.Triggers>
<Trigger Property="WindowState" Value="Normal">
<Setter Property="Visibility" Value="Collapsed" TargetName="RestoreButton" />
<Setter Property="Visibility" Value="Visible" TargetName="MaximizeButton" />
</Trigger>
<Trigger Property="WindowState" Value="Maximized">
<Setter Property="Visibility" Value="Visible" TargetName="RestoreButton" />
<Setter Property="Visibility" Value="Collapsed" TargetName="MaximizeButton" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
Also do command binding for proper working of Fake Chrome bar
public MainWindow()
{
this.CommandBindings.Add(new CommandBinding(SystemCommands.CloseWindowCommand, OnCloseWindow));
this.CommandBindings.Add(new CommandBinding(SystemCommands.MaximizeWindowCommand, OnMaximizeWindow, OnCanResizeWindow));
this.CommandBindings.Add(new CommandBinding(SystemCommands.MinimizeWindowCommand, OnMinimizeWindow, OnCanMinimizeWindow));
this.CommandBindings.Add(new CommandBinding(SystemCommands.RestoreWindowCommand, OnRestoreWindow, OnCanResizeWindow));
}
private void OnCanMinimizeWindow(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = this.ResizeMode != ResizeMode.NoResize;
}
private void OnCanResizeWindow(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = this.ResizeMode == ResizeMode.CanResize || this.ResizeMode == ResizeMode.CanResizeWithGrip;
}
private void OnCloseWindow(object sender, ExecutedRoutedEventArgs e)
{
Microsoft.Windows.Shell.SystemCommands.CloseWindow(this);
}
private void OnMaximizeWindow(object sender, ExecutedRoutedEventArgs e)
{
Microsoft.Windows.Shell.SystemCommands.MaximizeWindow(this);
}
private void OnMinimizeWindow(object sender, ExecutedRoutedEventArgs e)
{
Microsoft.Windows.Shell.SystemCommands.MinimizeWindow(this);
}
private void OnRestoreWindow(object sender, ExecutedRoutedEventArgs e)
{
Microsoft.Windows.Shell.SystemCommands.RestoreWindow(this);
}
My xaml is not exactly alike (I do not use WindowChrome but my own and I have a titlebar template), but I had the exact same problem and the solution should be usable for you as well.
First the easy one: for the doubleclick to work just use the ClickCount.
Then, geting the menu disappear requires keeping some state telling whether it is currently active or not: the trick is that different events are fired on the second click (as figured out by using Snoop. The first click is only a MousweDown, the second is MouseDown followed by MouseUp (my guess is that the up from the first click is handled by the sysmenu).
private bool inSysMenu = false;
void SystemMenuIcon_MouseDown( object sender, MouseButtonEventArgs e )
{
if( e.ClickCount == 1 && !inSysMenu )
{
inSysMenu = true;
ShowSystemMenu(); //replace with your code
}
else if( e.ClickCount == 2 && e.ChangedButton == MouseButton.Left )
{
window.Close();
}
}
void SystemMenuIcon_MouseLeave( object sender, MouseButtonEventArgs e )
{
inSysMenu = false;
}
void SystemMenuIcon_MouseUp( object sender, MouseButtonEventArgs e )
{
inSysMenu = false;
}