WPF Call Event handler from Style in Xaml - c#

I'm working on a custom WPF combobox control with a template shown in the image below.
As you can see, there is a TextBox (for filtering) and a button (for creating new record). So I create the class AdvComboBox with two events Search, CreateNew. My question is : How to call the handlers in the AdvComboBox class of these events from the control template ?
public class AdvComboBox : ComboBox
{
public event TextChangedEventHandler Search;
protected virtual void OnSearch(TextChangedEventArgs e)
{
TextChangedEventHandler handler = Search;
if (handler != null) handler(this, e);
}
public event EventHandler CreateNew;
protected virtual void OnCreateNew()
{
EventHandler handler = CreateNew;
if (handler != null)
handler(this, EventArgs.Empty);
}
}
The Popup Part :
<Popup x:Name="PART_Popup" AllowsTransparency="True" Grid.ColumnSpan="2" IsOpen="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Margin="1" PopupAnimation="{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}" Placement="Bottom">
<themes:SystemDropShadowChrome x:Name="shadow" Color="Transparent" MaxHeight="{TemplateBinding MaxDropDownHeight}" MinWidth="{Binding ActualWidth, ElementName=templateRoot}">
<Border x:Name="DropDownBorder" BorderBrush="{DynamicResource {x:Static SystemColors.WindowFrameBrushKey}}" BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="35"/>
<RowDefinition Height="*"/>
<RowDefinition Height="35"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0"
Orientation="Horizontal">
<!--TODO : Should Call OnSearch-->
<TextBox Width="230"
Margin="5 0 0 0"
Height="26"
VerticalContentAlignment="Center">
</TextBox>
</StackPanel>
<ScrollViewer Grid.Row="1" x:Name="DropDownScrollViewer">
<Grid x:Name="grid" RenderOptions.ClearTypeHint="Enabled">
<Canvas x:Name="canvas" HorizontalAlignment="Left" Height="0" VerticalAlignment="Top" Width="0">
<Rectangle x:Name="OpaqueRect" Fill="{Binding Background, ElementName=DropDownBorder}" Height="{Binding ActualHeight, ElementName=DropDownBorder}" Width="{Binding ActualWidth, ElementName=DropDownBorder}"/>
</Canvas>
<ItemsPresenter x:Name="ItemsPresenter" KeyboardNavigation.DirectionalNavigation="Contained" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Grid>
</ScrollViewer>
<StackPanel Grid.Row="2"
Margin="5">
<!--TODO : Should Call OnCreateNew-->
<Button Content="Create new record"
Name="BnCreateNew"
Width="Auto"
Padding="3"
HorizontalAlignment="Right">
</Button>
</StackPanel>
</Grid>
</Border>
</themes:SystemDropShadowChrome>
</Popup>
AdvComboBox in Xaml
<local:AdvComboBox
HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="250"
Height="30"
VerticalContentAlignment="Center"
Style="{DynamicResource AdvComboBoxStyle1}"
Search="AdvComboBox_OnSearch"
CreateNew="AdvComboBox_OnCreateNew">
<ComboBoxItem Content="Item 1"/>
<ComboBoxItem Content="Item 2"/>
<ComboBoxItem Content="Item 3"/>
<ComboBoxItem Content="Item 4"/>
<ComboBoxItem Content="Item 5"/>
</local:AdvComboBox>
EDIT :
After reading the msdn article provided in #Marco response, I've used Routed events to solve my problem by subscribing to the TextBox.TextChanged event and Button.Click event. Some little logic were necessary in the Button.Click handler to differentiate between the click on the ToggleButton and the simple Button (Create new record).
<local:AdvComboBox
x:Name="CbCountries"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="250"
Height="30"
VerticalContentAlignment="Center"
Style="{DynamicResource AdvComboBoxStyle1}"
TextBox.TextChanged = "AdvComboBox_OnSearch"
Button.Click = "AdvComboBox_OnCreateNew"/>
Handlers
private void AdvComboBox_OnCreateNew(object sender, RoutedEventArgs e)
{
if (e.OriginalSource is ToggleButton)
return;
MessageBox.Show("Create new record !", "Hello");
}
private void AdvComboBox_OnSearch(object sender, RoutedEventArgs e)
{
var combobox = sender as AdvComboBox;
if (combobox == null)
return;
var textBox = e.OriginalSource as TextBox;
if (textBox == null)
return;
var itemsViewOriginal = (CollectionView)CollectionViewSource.GetDefaultView(combobox.ItemsSource);
itemsViewOriginal.Filter = (o =>
{
if (String.IsNullOrEmpty(textBox.Text))
return true;
if (((string)o).Contains(textBox.Text))
return true;
return false;
});
itemsViewOriginal.Refresh();
}

you need to cleare the events as RoutedEvents, the same way you need DependencyProperty. Something like this:
public static readonly RoutedEvent SearchEvent = EventManager.RegisterRoutedEvent(
"Search", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(AdvComboBox));
// Provide CLR accessors for the event
public event RoutedEventHandler Search
{
add { AddHandler(SearchEvent , value); }
remove { RemoveHandler(SearchEvent, value); }
}
Creating coltrols is not that simple. You need also to add a template for your control. And add a static constructor:
static AdvComboBox ()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(AdvComboBox), new FrameworkPropertyMetadata(typeof(AdvComboBox)));
}
And, add some attributes like:
[TemplatePart(Name = "PART_EditableTextBox", Type = typeof(TextBox))]
[TemplatePart(Name = "PART_Popup", Type = typeof(Popup))]
[TemplatePart(Name = "PART_CreateNewButton", Type = typeof(Button))]
[TemplatePart(Name = "PART_SearchTextBox ", Type = typeof(TextBox))]
[StyleTypedProperty(Property = "ItemContainerStyle", StyleTargetType = typeof(ComboBoxItem))]
public class AdvComboBox : ComboBox
{ ...
Check https://msdn.microsoft.com/en-us/library/cc295235.aspx for more information.

Related

Add UserControl dynamically in WPF C#

I've got one page in my WPF app that should display some "tiles" in number as I specify before. Tile looks like this:
So my page should look something like this:
It is achievable of course by manually cloning tiles, but I want to avoid this (achieve it in more programmatic way). So instead of creating 6 clones I should stick to only one and then if needed add remaining ones. How can I accomplish that? I guess I should create my own UserControl like this:
<Grid HorizontalAlignment="Left" Height="199" VerticalAlignment="Top" Width="207" Background="Black">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="0*"/>
</Grid.RowDefinitions>
<Image x:Name="image1" HorizontalAlignment="Left" Height="175" VerticalAlignment="Top" Width="207" Stretch="UniformToFill"/>
<Grid HorizontalAlignment="Left" Height="30" VerticalAlignment="Top" Width="112" Background="#FFC78D10">
<TextBox IsReadOnly = "True" x:Name="CategoryOfEvent" Height="30" TextWrapping="Wrap" Text="Category" Width="112" Background="{x:Null}" BorderBrush="{x:Null}" Foreground="White" FontSize="18" SelectionBrush="{x:Null}" HorizontalAlignment="Left" VerticalAlignment="Top" >
<TextBox.Template>
<ControlTemplate TargetType="{x:Type TextBox}">
<ScrollViewer Name="PART_ContentHost"/>
</ControlTemplate>
</TextBox.Template>
</TextBox>
</Grid>
<TextBox IsReadOnly = "True" x:Name="HourOfEvent" HorizontalAlignment="Left" Height="28" Margin="0,42,0,0" TextWrapping="Wrap" Text="Hour" VerticalAlignment="Top" Width="148" Background="{x:Null}" BorderBrush="{x:Null}" Foreground="#FFE2E2E2" FontSize="22" SelectionBrush="{x:Null}" FontWeight="Bold" TextChanged="HourOfEvent_TextChanged">
<TextBox.Template>
<ControlTemplate TargetType="{x:Type TextBox}">
<ScrollViewer Name="PART_ContentHost"/>
</ControlTemplate>
</TextBox.Template>
</TextBox>
<TextBox IsReadOnly = "True" x:Name="TitleOfEvent" HorizontalAlignment="Left" Height="88" Margin="0,82,0,0" TextWrapping="Wrap" Text="Title" VerticalAlignment="Top" Width="207" Background="{x:Null}" BorderBrush="{x:Null}" Foreground="White" FontSize="20" SelectionBrush="{x:Null}" FontWeight="Bold">
<TextBox.Template>
<ControlTemplate TargetType="{x:Type TextBox}">
<ScrollViewer Name="PART_ContentHost"/>
</ControlTemplate>
</TextBox.Template>
</TextBox>
<TextBox IsReadOnly = "True" x:Name="PlaceOfEvent" HorizontalAlignment="Left" Height="24" Margin="0,175,0,0" TextWrapping="Wrap" Text="Where" VerticalAlignment="Top" Width="207" Background="{x:Null}" BorderBrush="{x:Null}" Foreground="White" FontSize="14" SelectionBrush="{x:Null}">
<TextBox.Template>
<ControlTemplate TargetType="{x:Type TextBox}">
<ScrollViewer Name="PART_ContentHost"/>
</ControlTemplate>
</TextBox.Template>
</TextBox>
</Grid>
and just add them to my page. I would like also to mention that in every tiles there are 4 textboxes which are displaying some data parsed from Json, so maybe some automatic binding should do the job?
It is as simple as that.Firstly,what you can do is,create a UserControl with all your controls inside like TextBlocks and others.Then,decide which type of container control you want to use to hold your UserControl.Let's assume it's a grid.You can specify/set grid's column/rows for each user control.A sample :
private void addControl()
{
UserControl1 MyCon = new UserControl1;
MyGrid.Children.Add(MyCon);
Grid.SetRow(MyCon , 1); ////replace 1 with required row count
}
You can create grid rows in design time,or u can do it in code behind as well :
MyGrid.RowDefinitions.Add(new RowDefinition);
If you want to use columns instead,just apply same code but change Row/Rowdefinition with Column/ColumnDefinition
Hope this helps :)
The follwing example shows how to create multiple of the tiles you have been posting using a DataTemplate and WrapPanel. The DataTemplate specifies how an object (in this case a TileItem) is visualized. You can create multiple TileItems and then add them to an collection, in order to visualize them all.
Assuming your UI resides in MainWindow, you can create a collection with three items in it.
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
TileItemCollection = new ObservableCollection<TileItem>(new []
{
new TileItem(){Category = "Alpha", Hour = "10", Title = "Hello World", Where = "Office"},
new TileItem(){Category = "Beta", Hour = "15", Title = "Test", Where = "Home"},
new TileItem(){Category = "Gamma", Hour = "44", Title = "My Title", Where = "Work"},
});
DataContext = this;
}
public ObservableCollection<TileItem> TileItemCollection { get; }
}
You could load your Items from JSON and create an TileItem for each one in the JSON document. The class for TileItemss can be found below.
public class TileItem : INotifyPropertyChanged
{
private string _hour;
private string _title;
private string _where;
private string _category;
public string Category
{
get => _category;
set
{
if (value == _category) return;
_category = value;
OnPropertyChanged();
}
}
public string Hour
{
get => _hour;
set
{
if (value == _hour) return;
_hour = value;
OnPropertyChanged();
}
}
public string Title
{
get => _title;
set
{
if (value == _title) return;
_title = value;
OnPropertyChanged();
}
}
public string Where
{
get => _where;
set
{
if (value == _where) return;
_where = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Note that in order for datachanges to be propagated to the UI, all properties which should be updated in the UI when you update them in code need to raise the property changed event. In this example all properties do this by default.
You can then update the XAML to bind to a collection. The ItemsControl acts as a container for the tiles. If you scroll down further you may notice the use of WrapPanel which is responsible for the item wrapping effect when you resize the control.
<ItemsControl ItemsSource="{Binding TileItemCollection}" Margin="20">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:TileItem}" >
<Grid HorizontalAlignment="Left" Height="199" VerticalAlignment="Top" Width="207" Background="Black">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="0*"/>
</Grid.RowDefinitions>
<Image x:Name="image1" HorizontalAlignment="Left" Height="175" VerticalAlignment="Top" Width="207" Stretch="UniformToFill"/>
<Grid HorizontalAlignment="Left" Height="30" VerticalAlignment="Top" Width="112" Background="#FFC78D10">
<TextBox IsReadOnly="True" Height="30" TextWrapping="Wrap" Text="{Binding Path=Category}" Width="112" Background="{x:Null}" BorderBrush="{x:Null}" Foreground="White" FontSize="18" SelectionBrush="{x:Null}" HorizontalAlignment="Left" VerticalAlignment="Top" >
<TextBox.Template>
<ControlTemplate TargetType="{x:Type TextBox}">
<ScrollViewer Name="PART_ContentHost"/>
</ControlTemplate>
</TextBox.Template>
</TextBox>
</Grid>
<TextBox IsReadOnly="True" HorizontalAlignment="Left" Height="28" Margin="0,42,0,0" TextWrapping="Wrap" Text="{Binding Path=Hour}" VerticalAlignment="Top" Width="148" Background="{x:Null}" BorderBrush="{x:Null}" Foreground="#FFE2E2E2" FontSize="22" SelectionBrush="{x:Null}" FontWeight="Bold">
<TextBox.Template>
<ControlTemplate TargetType="{x:Type TextBox}">
<ScrollViewer Name="PART_ContentHost"/>
</ControlTemplate>
</TextBox.Template>
</TextBox>
<TextBox IsReadOnly="True" HorizontalAlignment="Left" Height="88" Margin="0,82,0,0" TextWrapping="Wrap" Text="{Binding Path=Title}" VerticalAlignment="Top" Width="207" Background="{x:Null}" BorderBrush="{x:Null}" Foreground="White" FontSize="20" SelectionBrush="{x:Null}" FontWeight="Bold">
<TextBox.Template>
<ControlTemplate TargetType="{x:Type TextBox}">
<ScrollViewer Name="PART_ContentHost"/>
</ControlTemplate>
</TextBox.Template>
</TextBox>
<TextBox IsReadOnly="True" x:Name="PlaceOfEvent" HorizontalAlignment="Left" Height="24" Margin="0,175,0,0" TextWrapping="Wrap" Text="{Binding Path=Where}" VerticalAlignment="Top" Width="207" Background="{x:Null}" BorderBrush="{x:Null}" Foreground="White" FontSize="14" SelectionBrush="{x:Null}">
<TextBox.Template>
<ControlTemplate TargetType="{x:Type TextBox}">
<ScrollViewer Name="PART_ContentHost"/>
</ControlTemplate>
</TextBox.Template>
</TextBox>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Template>
<ControlTemplate>
<ScrollViewer>
<ItemsPresenter />
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
</ItemsControl>
Each Tile is bound to an TileItem which means that the Bindings which point to e.g. Category, point to the Category of an TileItem.
To increase reusability it would be possible to move the code into its own usercontrol and optionally to add DependencyPropertys for better control.

How to access a specific item in itemscontrol and retrieve some data in UWP

I have an ItemsControl with DataTemplate in my Page.Xaml and the code is like below:
<ItemsControl x:Name="chatUI" VerticalAlignment="Bottom">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid x:Name="myGrid" Width="340" Background="{Binding Background}" HorizontalAlignment="{Binding GridHorizontalAlign}" Margin="10,0,10,10" MinHeight="45" BorderBrush="#FF003A4F" BorderThickness="0,0,0,2">
<Polygon Visibility="{Binding RightVisibility}" Fill="{Binding Background}" Points="0,0 5,6, 0,12" VerticalAlignment="Top" HorizontalAlignment="Right" Margin="0,0,-5,0" />
<Polygon Visibility="{Binding LeftVisibility}" Fill="{Binding Background}" Points="5,0 0,6, 5,12" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="-5,0,0,0" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="{Binding Text}" TextWrapping="Wrap" FontSize="15" FontFamily="Segoe UI" Foreground="White" Margin="10,10,10,0"/>
<TextBlock Grid.Row="1" Text="{Binding Time}" TextWrapping="Wrap" FontSize="11" FontFamily="Segoe UI" Foreground="LightGray" Margin="10,0,10,5" VerticalAlignment="Bottom" TextAlignment="Right"/>
</Grid>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
What I need right now is getting Text which is bound to the TextBlock when I right click on the grid named myGrid. How is that possible in C#?
We can add the RightTapped event of the Grid, it will be fired when you right click the Grid.
In the RightTapped event we can use Grid.Children to get the collection of child elements of the Grid. That we can get the Grid in the root Grid that named myGrid. That we can use the Grid.Children to get the TextBlock in the Grid.
For example:
private async void myGrid_RightTapped(object sender, RightTappedRoutedEventArgs e)
{
var RightTapGrid = sender as Grid;
var childernElements = RightTapGrid.Children;
foreach (var item in childernElements)
{
var grid = item as Grid;
if (grid != null)
{
var itemchildernElements = grid.Children;
foreach (var text in itemchildernElements)
{
var textBlock = text as TextBlock;
var dialog = new ContentDialog()
{
Title = textBlock.Text,
MaxWidth = this.ActualWidth
};
dialog.PrimaryButtonText = "OK";
dialog.SecondaryButtonText = "Cancel";
await dialog.ShowAsync();
break;
}
}
}
}
If you get your Binding Data from Class called ClassName
You can try this code
XAML:
<ListView x:Name="chatUI" VerticalAlignment="Bottom" SelectionChanged="chatUI_SelectionChanged">
<ListView.ItemTemplate>
<DataTemplate>
<Grid x:Name="myGrid" Width="340" Background="{Binding Background}" HorizontalAlignment="{Binding GridHorizontalAlign}" Margin="10,0,10,10" MinHeight="45" BorderBrush="#FF003A4F" BorderThickness="0,0,0,2">
<Polygon Visibility="{Binding RightVisibility}" Fill="{Binding Background}" Points="0,0 5,6, 0,12" VerticalAlignment="Top" HorizontalAlignment="Right" Margin="0,0,-5,0" />
<Polygon Visibility="{Binding LeftVisibility}" Fill="{Binding Background}" Points="5,0 0,6, 5,12" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="-5,0,0,0" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="{Binding Text}" TextWrapping="Wrap" FontSize="15" FontFamily="Segoe UI" Foreground="White" Margin="10,10,10,0"/>
<TextBlock Grid.Row="1" Text="{Binding Time}" TextWrapping="Wrap" FontSize="11" FontFamily="Segoe UI" Foreground="LightGray" Margin="10,0,10,5" VerticalAlignment="Bottom" TextAlignment="Right"/>
</Grid>
</Grid>
</DataTemplate>
</Listview.ItemTemplate>
And add SelectionChanged Event :
private void chatUI_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ListView view = (ListView)sender;
//Get Selected Item
ClassName class = view.SelectedItem as ClassName;
string path = class.Text;
// Now we have Text of selected item in Listview
}

Grid animation (button color change)

I would like to create an animation within my grid. I have a 5x5 Grid, each grid shows a button. After the grid is loaded one of the buttons should randomliy change his color to green. After 1 seconds this button should change back and another should change his color to green.
If the user is able to reach the this button within this 1 second with his mouse (mouseover) the button should change his color to red and stay red. The next button who changes his color to green should not be this one.
This should be a little game. My question is, what is the easiest way to implement this game.
Please help me!
<Page x:Class="LeapTest.Layout"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:LeapTest"
mc:Ignorable="d"
d:DesignHeight="1050" d:DesignWidth="1000"
Title="Layout">
<Page.Resources>
<Style x:Key="pageTitle" TargetType="TextBlock">
<Setter Property="Background" Value="DimGray"/>
<Setter Property="FontSize" Value="40"/>
<Setter Property="FontFamily" Value="Arial"/>
<Setter Property="TextAlignment" Value="Center"/>
<Setter Property="Padding" Value="0,5,0,5"/>
</Style>
<Style x:Key="Grid" TargetType="Grid">
<Setter Property="Background" Value="White"/>
</Style>
<Style x:Key="Button" TargetType="Button">
<Setter Property="Background" Value="White"/>
<Setter Property="BorderBrush" Value="Green"/>
<Setter Property="BorderThickness" Value="2"/>
</Style>
</Page.Resources>
<Grid Style="{StaticResource Grid}">
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="200" />
<RowDefinition Height="200" />
<RowDefinition Height="200" />
<RowDefinition Height="200" />
<RowDefinition Height="200" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
<ColumnDefinition Width="200" />
<ColumnDefinition Width="200" />
<ColumnDefinition Width="200" />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Grid.ColumnSpan="5" Style="{StaticResource pageTitle}"> LEAP Motion </TextBlock>
<Button Name ="BTN_0_0" Grid.Column="0" Grid.Row="1" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_0_1" Grid.Column="1" Grid.Row="1" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_0_2" Grid.Column="2" Grid.Row="1" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_0_3" Grid.Column="3" Grid.Row="1" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_0_4" Grid.Column="4" Grid.Row="1" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_1_0" Grid.Column="0" Grid.Row="2" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_1_1" Grid.Column="1" Grid.Row="2" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_1_2" Grid.Column="2" Grid.Row="2" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_1_3" Grid.Column="3" Grid.Row="2" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_1_4" Grid.Column="4" Grid.Row="2" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_2_0" Grid.Column="0" Grid.Row="3" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_2_1" Grid.Column="1" Grid.Row="3" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_2_2" Grid.Column="2" Grid.Row="3" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_2_3" Grid.Column="3" Grid.Row="3" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_2_4" Grid.Column="4" Grid.Row="3" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_3_0" Grid.Column="0" Grid.Row="4" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_3_1" Grid.Column="1" Grid.Row="4" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_3_2" Grid.Column="2" Grid.Row="4" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_3_3" Grid.Column="3" Grid.Row="4" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_3_4" Grid.Column="4" Grid.Row="4" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_4_0" Grid.Column="0" Grid.Row="5" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_4_1" Grid.Column="1" Grid.Row="5" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_4_2" Grid.Column="2" Grid.Row="5" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_4_3" Grid.Column="3" Grid.Row="5" Click="BTN_Click" Style="{StaticResource Button}"/>
<Button Name ="BTN_4_4" Grid.Column="4" Grid.Row="5" Click="BTN_Click" Style="{StaticResource Button}"/>
</Grid>
//BackEnd Code
private void BTN_Click(object sender, RoutedEventArgs e)
{
//BTN1.Background = Brushes.Green;
Button btnTest = (Button)sender;
if (btnTest.Background == Brushes.Green)
{
btnTest.Background = Brushes.White;
}
else
{
btnTest.Background = Brushes.Green;
}
}
you should loop over all the button controls on the form with a special name or tag. Save this in an array and select one at random and change it's color. if the mouseover finds the one with the right color recall the method to find a control at random and so on.
This should be pretty straight forward. And since this looks like homework. I won't solve it for you as you wouldn't learn anything. Think about what needs to be done and google each part seperately. try to understand it and then continue! goodluck!
EDIT : regarding your question in a strict way, you do not need animation to acheive what you want to do, unless your need is to animate color changes.
Here is a full working example not using MVVM but using a collection of models representing the buttons.
The main window/page code handling all the logic (Random, model changes, etc.):
public partial class MainWindow : Window, INotifyPropertyChanged
{
private const int BTN_NUMBERS = 25;
private ObservableCollection<ButtonModel> _buttonsCollection;
private ButtonModel _currentTarget;
public ObservableCollection<int> ExcludedItems
{
get { return _excludedItems; }
private set { _excludedItems = value; OnPropertyChanged(); }
}
private Random _rnd;
private Timer _timer;
private ObservableCollection<int> _excludedItems = new ObservableCollection<int>();
public MainWindow()
{
DataContext = this;
InitializeComponent();
ButtonsCollection = new ObservableCollection<ButtonModel>();
for (int i = 0; i < BTN_NUMBERS; i++)
{
ButtonsCollection.Add(new ButtonModel() { ButtonNumber = i });
}
Start();
}
private void Start()
{
_currentTarget = null;
foreach (var bm in ButtonsCollection)
{
bm.IsCurrentTarget = bm.IsReached = false;
}
ExcludedItems.Clear();
_rnd = new Random(DateTime.Now.Second);
_timer = new Timer(OnTargetChanged, null, 0, 1000);
}
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<ButtonModel> ButtonsCollection
{
get { return _buttonsCollection; }
set { _buttonsCollection = value; OnPropertyChanged(); }
}
void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void Btn_candidate_OnMouseEnter(object sender, MouseEventArgs e)
{
ButtonModel model = ((Button)sender).DataContext as ButtonModel;
if (model != null && model.IsCurrentTarget && !ExcludedItems.Contains(model.ButtonNumber))
{
model.IsReached = true;
ExcludedItems.Add(model.ButtonNumber);
}
}
private void ChangeTarget()
{
var target = GetNextTarget();
if (target == -1)
{
if (_timer != null)
{
_timer.Dispose();
_timer = null;
}
MessageBox.Show("All items have been reached ! Congratulations");
}
if (_currentTarget != null) _currentTarget.IsCurrentTarget = false;
_currentTarget = ButtonsCollection[target];
_currentTarget.IsCurrentTarget = true;
}
private int GetNextTarget()
{
if (ExcludedItems.Count == BTN_NUMBERS)
{
return -1;
}
var target = _rnd.Next(0, BTN_NUMBERS);
if (ExcludedItems.Contains(target))
{
return GetNextTarget();
}
return target;
}
private void OnTargetChanged(object state)
{
this.Dispatcher.InvokeAsync(ChangeTarget);
}
private void Btn_startover_OnClick(object sender, RoutedEventArgs e)
{
Start();
}
}
The model representing the buttons, that contains the code that triggers XAML changes :
public class ButtonModel : INotifyPropertyChanged
{
private bool _isCurrentTarget;
private bool _isReached;
public event PropertyChangedEventHandler PropertyChanged;
public int ButtonNumber { get; set; }
public bool IsCurrentTarget
{
get { return _isCurrentTarget; }
set { _isCurrentTarget = value; OnPropertyChanged(); }
}
public bool IsReached
{
get { return _isReached; }
set { _isReached = value; OnPropertyChanged(); }
}
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
And finally, and most importantly, the simplified XAML code :
<Window x:Class="GridAnimame.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:GridAnimame"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance {x:Type local:MainWindow}}"
Title="MainWindow" Height="500" Width="500">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="400"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="400"/>
<RowDefinition/>
</Grid.RowDefinitions>
<ItemsControl ItemsSource="{Binding ButtonsCollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Name="btn_candidate" MouseEnter="Btn_candidate_OnMouseEnter" Margin="1">
<Button.Template>
<ControlTemplate TargetType="Button">
<Border x:Name="brd_btn_layout" Background="LightGray" BorderBrush="Black" BorderThickness="1">
<TextBlock Text="{Binding ButtonNumber}" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="Black"></TextBlock>
</Border>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding IsCurrentTarget}" Value="true">
<Setter TargetName="brd_btn_layout" Property="Background" Value="Green"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding IsReached}" Value="true">
<Setter TargetName="brd_btn_layout" Property="Background" Value="Red"></Setter>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Button.Template>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ListBox Grid.Row="0" Grid.Column="1" ItemsSource="{Binding ExcludedItems}"></ListBox>
<Button x:Name="btn_startover" Grid.Row="1" Grid.Column="1" Content="Start Over !" Margin="2" Click="Btn_startover_OnClick"></Button>
</Grid>
Althought the logic could be improved, here are the key points of this sample :
You don't declare all the buttons on a static way in the XAML. Instead, your main model holds a collection of models representing the buttons and containing all the data that will trigger the XAML (visual) changes. Thus, the control in XAML representing the grid is bound to this collection
The logic is made throught code and the visual effect of this loggic is reflected using triggers declared in XAML
Only 2 things still have to be done to have a clean approach : 1)The main window logic can now be easily be encapsulated into a real view model which will serve as the window data context and 2) the events should be replaced by DelegateCommands

C# WPF change grid visibility if combobox item is selected

I'm trying to display a Gridand its contents when an item (anyone) in combobox is selected. If nothing is selected, grid will remain hidden.
XAML
<ComboBox x:Name="cb" HorizontalAlignment="Left" VerticalAlignment="Top" Width="140" Height="25"/>
<Grid x:Name="gr" Visibility="Hidden">
<Border BorderThickness="1" HorizontalAlignment="Left" Height="600" VerticalAlignment="Top" Width="346">
<Border BorderThickness="1" RenderTransformOrigin="0.5,0.5">
</Border>
</Grid>
I've tried with this:
XAML.CS
public void ChangeVisibility(ComboBox cb, Grid gr)
{
if (cb.SelectedItem != null)
{
gr.Visibility = Visibility.Visible;
}
else
{
gr.Visibility = Visibility.Hidden;
}
But it doesn't change anything. I've tried in multiple ways, even with string.IsNullOrEmpty.
Source of combobox is a List<string>.
EDIT
Method is called here
public MainWindow()
{
InitializeComponent();
WindowStartupLocation = WindowStartupLocation.CenterScreen;
ChangeVisibility(cb, gr);
}
<ComboBox x:Name="cb" HorizontalAlignment="Left" VerticalAlignment="Top" Width="140" Height="25"/>
<Grid x:Name="gr">
<Grid.Style>
<Style TargetType="Grid">
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=cb, Path=SelectedItem}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<Border BorderThickness="1" HorizontalAlignment="Left" Height="600" VerticalAlignment="Top" Width="346">
<Border BorderThickness="1" RenderTransformOrigin="0.5,0.5">
</Border>
</Grid>
Try with
MainWindow.xaml.cs
private void ComboBox_Selected(object sender, RoutedEventArgs e)
{
var item = Combo.SelectedItem as ComboBoxItem;
if (item.Content.ToString() == "Visible")
{
RedGrid.Visibility = System.Windows.Visibility.Visible;
}
else
{
RedGrid.Visibility = System.Windows.Visibility.Hidden;
}
}
MainWindow.xaml:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition />
</Grid.RowDefinitions>
<ComboBox Name="Combo" SelectionChanged="ComboBox_Selected">
<ComboBoxItem Content="Visible" />
<ComboBoxItem Content="Hidden" />
</ComboBox>
<Grid Name="RedGrid" Grid.Row="1" Background="Red">
</Grid>
</Grid>
Or to be more similar to your problem:
MainWindow.xaml.cs:
private void ComboBox_Selected(object sender, RoutedEventArgs e)
{
var item = Combo.SelectedItem as ComboBoxItem;
if (item != null)
{
RedGrid.Visibility = System.Windows.Visibility.Visible;
}
}
MainWindow.xaml:
<ComboBox Name="Combo" SelectionChanged="ComboBox_Selected" >
<ComboBoxItem Content="Element 1" />
<ComboBoxItem Content="Element 2" />
</ComboBox>
<Grid Name="RedGrid" Grid.Row="1" Background="Red" Visibility="Hidden">
</Grid>
But I am not sure, that your approach is correct. You cannot easily deselect item in ComboBox so basically if you select anything Grid will be always visible. I will rather chose solution with multiple ComboBoxItem where one of them will be "none", "hide" or sth like that.
You can always think about MVVM pattern which is very popular with WPF framework

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