I am new in coding and atm trying to understand events what is an annoying stage but super important I guess. I just made a tic tac toe game and it is working but not really "beautiful" coded. I really have problems in using the events. well I am reading 3 different books, google is my best friend and I guess I red all the StackOverflow posts about events but the bulb in my head is never shining :P so I will give you boys a part of my code and I added some comments for the understanding:
/*I have 9 buttons(3x3) which are the play field */
private void Feld1_Click(object sender, RoutedEventArgs e)
{
// in my game each player have 1 Radiobutton so they check a RButton and then it's their turn
if (Player1RButton.IsChecked == true)
{
// i dont wanted to use "X" or "O" so i chose the colors green and yellow
Feld1.Background = Brushes.Green;
// Feld1G is for example that Player1 (green) is "owning" this
// field/button so i can later check who won the game
Feld1G = 1;
Feld1Y = 0;
}
if (Player2RButton.IsChecked == true)
{
//here is the same thing happening like in the example of green
Feld1.Background = Brushes.Yellow;
Feld1Y = 1;
Feld1G = 0;
}
}
private void Feld2_Click(object sender, RoutedEventArgs e)
{
if (Player1RButton.IsChecked == true)
{
Feld2.Background = Brushes.Green;
Feld2G = 1;
Feld2Y = 0;
}
if (Player2RButton.IsChecked == true)
{
Feld2.Background = Brushes.Yellow;
Feld2Y = 1;
Feld2G = 0;
}
}
here is an example how the ui looks like:tic tac toe exampe
now what I would like to do in my words cause I don't know how to code it:
// I have no idea if this is the right start
public void OnClick (EventArgs e)
{
/* now I guess here have to happen something like this, for example, field9 was clicked and radiobutton2 is checked: know that button9 have been clicked know radiobutton is checked and now brush (this.button?) button/field9 and set Feld9Y=1;
}
*/
I want to make it a bit more clearly here: I want all the functions run from the method above and not in each button event for itself
so my questions:
1. what do I have to do to make this work the way i explained above to make 1 method for all of my buttons
and it would be great if you boys could make a good story why I have to use it this way and how it works so a brainless ape like me can understand the event stuff and the bulb will finally shine bright like a diamond :P
Edit:here is the link for my whole code:
https://codereview.stackexchange.com/questions/164462/c-events-in-a-tic-tac-toe-game
Here is a quick example to get you started. I only implemented changing the background and nothing more.
Below is the xaml. I set up a very simple board with 9 regular buttons and 2 radio buttons. I did not implement anything else.
<Grid x:Name="board">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Button Content="Button"/>
<Button Content="Button"
Grid.Column="1" />
<Button Content="Button"
Grid.Column="2"/>
<Button Content="Button"
Grid.Row="1" />
<Button Content="Button"
Grid.Row="1"
Grid.Column="1" />
<Button Content="Button"
Grid.Row="1"
Grid.Column="2" />
<Button Content="Button"
Grid.Row="2" />
<Button Content="Button"
Grid.Row="2"
Grid.Column="1" />
<Button Content="Button"
Grid.Row="2"
Grid.Column="2" />
<RadioButton x:Name="playerOneRadioButton"
IsChecked="True"
Content="Player 1"
Grid.Column="3"
HorizontalAlignment="Left"
Margin="10,10,0,0"
Grid.Row="1"
VerticalAlignment="Top" />
<RadioButton x:Name="playerTwoRadioButton"
Content="Player 2"
Grid.Column="3"
HorizontalAlignment="Left"
Margin="10,30,0,0"
Grid.Row="1"
VerticalAlignment="Top" />
</Grid>
Below is the code behind.
I hooked up each buttons click event to point to the Button_Click method.
In that method I first cast the sender to type Button. I then change the background color of the button to either yellow or green.
public partial class MainWindow: Window
{
public MainWindow( )
{
InitializeComponent( );
// Iterate through all of the child elements
// contained in the Grid control which I named "board".
foreach( var control in board.Children.OfType<Button>( ) )
{
// Hook up the event handler of each button.
control.Click += Button_Click;
}
}
private void Button_Click( object sender, RoutedEventArgs e )
{
// Safe cast the sender to type Button.
var button = sender as Button;
if( button == null ) return;
// Change the background color of the button
// yellow or green based on which radio button
// is checked.
button.Background = playerOneRadioButton.IsChecked.Value ? Brushes.Green : Brushes.Yellow;
}
}
EDIT:
Question 1: The Grid which I have on the design surface is named "board". Children is a property of the Grid control. All child elements (elements that reside inside of the grid, like the buttons) are added to the Children property of the grid control. The Children property is a collection, or you could say a list, of all child elements that reside in the grid control. I loop through each child control in the grid that is of type button.
Question 2: The ?: is called a ternary operator. It is used for writing a conditional statement (an if statement). You can also write it as follows:
if( playerOneRadioButton.IsChecked.Value )
{
button.Background = Brushes.Green;
}
else
{
button.Background = Brushes.Yellow;
}
Question 3: I'm currently casting the object sender to type button using whats called a "safe cast". In other words, if the cast fails (say sender wasn't a button and was some other control instead) then null is returned. I check for this null condition to ensure that the cast was successful. If the button variable is null then the cast was not successful and I want to exit (return) out of that method. No code below the
if(button == null) return;
will execute.
The single method you want to use should look like this:
private void Feld_Click(object sender, RoutedEventArgs e)
{
var currentButton = (Button)sender;
if (Player1RButton.IsChecked == true)
{
currentButton.Background = Brushes.Green;
}
}
Add this as OnClick handler for all the buttons.
From there you can calculate the Feld color whenever you need it, by accessing the Background property in a separate method.
Related
I'm working with an item template that should display one or more buttons per item. There are three buttons that I want to be displayed on a horizontal line. Each button has an icon and some text.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button x:Name="LeftButton"
Text="Left Button"
ImageSource="left.png"
HorizontalOptions="Start"/>
<Button x:Name="CenterButton"
Text="Center Button"
ImageSource="center.png"
HorizontalOptions="Center"/>
<Button x:Name="RightButton"
Text="Right Button"
ImageSource="right.png"
HorizontalOptions="End"/>
</Grid>
This is working good so far. The three buttons display the icon and text and they are aligned left, center and right respectively.
Now, I want the buttons to only display the icons, if the container is not wide enough to display all the text. As far as I understand MVVM, this should be the responsibility of the View.
My intended solution would be something like this:
public partial class ItemTemplate : ContentView
// ContentView is our implementation of a UI element.
{
public ItempTemplate()
{
InitializeComponent();
SizeChanged += HandleSizeChanged;
}
private void HandleSizeChanged(object sender, EventArgs e)
{
if (/* not enough space */)
{
LeftButton.Text = string.Empty;
CenterButton.Text = string.Empty;
RightButton.Text = string.Empty;
}
else
{
LeftButton.Text = "Left Button";
CenterButton.Text = "Center Button";
RightButton.Text = "Right Button";
}
}
}
Is there any way to know if the container is wide enough for all three button? If possible, I'd like to use a dynamic solution, because the button text will eventually be translated.
You can place the code into a custom contentview , and decide to hide/show the text in the event LayoutChanged according to the container's width .
Custom View xaml
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button x:Name="LeftButton" Grid.Column="0" Text="Left Button" ImageSource="dots.png" HorizontalOptions="Start"/>
<Button x:Name="CenterButton" Grid.Column="1" Text="Center Button" ImageSource="dots.png" HorizontalOptions="Center"/>
<Button x:Name="RightButton" Grid.Column="2" Text="Right Button" ImageSource="dots.png" HorizontalOptions="End"/>
</Grid>
Custom View code behind
public partial class MyView : ContentView
{
public MyView()
{
InitializeComponent();
this.LayoutChanged += MyView_LayoutChanged;
}
private void MyView_LayoutChanged(object sender, EventArgs e)
{
var view = sender as View;
if (view.Width < 200)
{
LeftButton.Text = string.Empty;
CenterButton.Text = string.Empty;
RightButton.Text = string.Empty;
}
else
{
LeftButton.Text = "Left Button";
CenterButton.Text = "Center Button";
RightButton.Text = "Right Button";
}
}
}
Scenario : container is large enough.
xmlns:local="clr-namespace:FormsApp"
<local:MyView/>
Scenario : container is small ,wrapped inside another layout .
<Grid HorizontalOptions="Start" WidthRequest="199" >
<local:MyView/>
</Grid>
I solved my problem by using a custom button implementation (mostly because I needed some other additional features).
The custom button contains an Image and a Label. In addition to the standard button features I need, I added these methods to the code-behind:
public void ExpandText() {
Label.IsVisible = true;
}
public void CollapseText() {
Label.IsVisible = false;
}
public bool IsTextCollapsed() {
return !Label.IsVisible;
}
public double GetWidthAsExpanded() {
return Image.Width + Label.Width;
}
In the container's code-behind I check if the button's width fits within its container and collapse/expand accordingly. For that to work, I added containers for each button.
public ItemTemplate() {
InitializeComponent();
LayoutChanged += HandleLayoutChanged;
}
private static void HandleLayoutChanged(object sender, EventArgs e)
{
if (!(sender is ItemTemplate itemTemplate))
{
return;
}
if (itemTemplate.ButtonContainerLeft.Width > itemTemplate.ButtonLeft.GetWidthAsExpanded()
&& itemTemplate.ButtonContainerCenter.Width > itemTemplate.ButtonCenter.GetWidthAsExpanded()
&& itemTemplate.ButtonContainerRight.Width > itemTemplate.ButtonRight.GetWidthAsExpanded())
{
ExpandAllButtons(itemTemplate);
}
else
{
CollapseAllButtons(itemTemplate);
}
}
Because I added ExpandText and CollapseText earlier, I don't have to "remember" what the text inside the button was, because I just collapse the label within the button. GetWidthAsExpanded will always return the necessary width of the button, even if it is collapsed.
side-note 1: I could've just added the width-check within the custom button implementation, but not every ItemTemplate has all buttons and if any label is collapsed, all labels should be collapsed.
side-note 2: I needed button containers either way, because the left button will either be "Mark as Read" or "Mark as Unread" depending on the state of the ItemTemplate data context. So there are actually two buttons in the first container.
I have a simple dialog with a SpinEdit and two buttons: OK_Button and Cancel_Button. I've set a mask for the value in the SpinEdit and the dialog won't let me press the cancel button when the value is invalid. I've tried changing the SpinEdit's property to InvalidValueBehavior="AllowLeaveEditor" but then I can click both, OK and cancel button. Is there a way to ONLY allow pressing cancel when the value is incorrect?
XAML:
<dxe:SpinEdit x:Name="dxSpinEdit"
Height="23" MinWidth="200" Width="Auto"
HorizontalAlignment="Right"
Text="{Binding Value, Mode=TwoWay}"
MaskType="Numeric"
IsFloatValue="{Binding FloatValue}"
MinValue="{Binding MinValue}"
MaxValue="{Binding MaxValue}"
Mask="{Binding Mask, Mode=OneWay}"
MaxLength="{Binding Path=InputLength}"
MaskShowPlaceHolders="{Binding ShowPlaceHolder}"
InvalidValueBehavior="WaitForValidValue"
/>
<StackPanel Grid.Row="1" x:Uid="OKCancel_Buttons" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Bottom">
<Button Height="23" x:Name="OK_Button" Click="OK_Click" Content="OK" IsDefault="True" HorizontalAlignment="Right" MinWidth="95" />
<Button Height="23" x:Name="Cancel_Button" Click="Cancel_Click" Content="Cancel" HorizontalAlignment="Right" MinWidth="95" PreviewMouseDown="win_PreviewMouseDown" />
</StackPanel>
I looked up this issue on the devexpress forum but their solution didn't work for me. I've implemented the MouseDownPreview event like so:
C# (code behind)
private void OK_Click(object sender, RoutedEventArgs e)
{
DialogResult = true;
Close();
}
private void Cancel_Click(object sender, RoutedEventArgs e)
{
DialogResult = false;
Close();
}
private void win_PreviewMouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if(e.Source == Cancel_Button)
{
DialogResult = false;
Close();
}
}
But the event wasn't handled at all. I'd like to keep the property InvalidValueBehavior at the value "WaitForValidValue" but at the same time I'd like to allow pressing the Cancel button.
Even if you're not going to go the full MVVM route, you should switch from using click events to an ICommand implementation that supports CanExecute logic (such as this one from MVVM Light).
Using a command will automatically disable any bound control (e.g. button or menu item) when CanExecute is false. You can then have all the logic for controlling your commands grouped in one place, including validation that will only allow OK to be clicked when your object is in a valid state.
If you just want to go the standard WPF (non MVVM) route, you could add something like this in your window's constructor
public MyView()
{
....
Ok_Button.Command =
new RelayCommand(() => DialogResult = true, // just setting DialogResult is sufficient, no need to call Close()
// put the required validation logic here
() => dxSpinEdit.Value > 0 && dxSpinEdit.Value < 10);
Cancel_Button.Command = new RelayCommand(() => DialogResult = false);
// replace this with the actual event from SpinEdit
dxSpinEdit.ValueChanged += (s,e) => (OK_Button.Command as RelayCommand).RaiseCanExecuteChanged();
}
Yes I know it looks ugly 😀 - I'd suggest following the MVVM design pattern instead. When using MVVM, all of the command functionality belongs in your ViewModel.
Either way, you can then remove all the click and mousedown handlers from your buttons.
Sorry in advance if the title is confusing. Here's the situation. I have a grid called grdFilters. This grid has a series of CheckBoxes within it (one per row). Normally this grid is hidden. But I wanted it to show up when prompted (on button click) and leave when the user clicks somewhere other than the grid. To handle outside control mouse clicks I tried first capturing the mouse as such:
AddHandler(Mouse.PreviewMouseDownOutsideCapturedElementEvent, new MouseButtonEventHandler(HandleClickOutsideOfControl));
private void HandleClickOutsideOfControl(object sender, MouseButtonEventArgs e)
{
if (this.filters) //Check if the Filters grid is visible
{
ShowHideMenu("sbHideFilters", grdFilters); //Method that hides the grid
Mouse.Capture(null); //Release the mouse
}
}
private void btnFilters_Click(object sender, RoutedEventArgs e)
{
if (!this.filters) //Check if the filters grid is shown
{
ShowHideMenu("sbShowFilters", grdFilters); //Method that reveals the grid
Mouse.Capture(grdFilters); //Capture the mouse
}
}
The problem is that while the Filters grid has captured the mouse, none of the grids children (the Check Boxes) can be clicked on. I would really like to find a way to detect when the mouse is clicked outside of the grid while still allowing the children of the grid to accept mouse down events. Any help would be greatly appreciated, thanks in advance.
As per request here is some of my Xaml:
<Grid>
<Label x:Name="label" Content="Events" HorizontalAlignment="Center" VerticalAlignment="Top"/>
<ScrollViewer HorizontalAlignment="Left" Height="619" Margin="0,26,0,0" VerticalAlignment="Top" Width="450" VerticalScrollBarVisibility="Hidden">
<Grid x:Name="Schedule" HorizontalAlignment="Left" Height="Auto" VerticalAlignment="Top" Width="450" Margin="10,0,0,0"/>
</ScrollViewer>
<Grid x:Name="grdFilters" HorizontalAlignment="Left" Height="619" Margin="490,26,-176,0" VerticalAlignment="Top" Width="135" Background="{StaticResource TransparentBackground}" Panel.ZIndex="95">
<CheckBox x:Name="chckAll" Content="All" HorizontalAlignment="Left" Margin="0,10,0,0" VerticalAlignment="Top" Checked="chckAll_Checked" Unchecked="chckAll_Unchecked"/>
<Grid x:Name="grdFilters" HorizontalAlignment="Left" Height="588" Margin="0,31,0,0" VerticalAlignment="Top" Width="136"/>
</Grid>
<Button x:Name="btnFilters" Content="" Margin="436,223,-18,0" VerticalAlignment="Top" Background="Cyan" Opacity="0.15" Style="{StaticResource MyTabStyle}" Height="80" Click="btnFilters_Click"/>
</Grid>
The only thing I left out were the Resource Dictionaries and the page definition itself.
I think the Mouse.Capture and PreviewMouseDownOutsideCapturedElementEvent and are too specific for what you want.
I would rather use a hitResultsList, which can be used in a lot of different scenarios:
I slightly modified eh AddHandler
AddHandler(Mouse.PreviewMouseDownEvent, new MouseButtonEventHandler(HandleMouseDown));
And I added the VisualTreeHelper.HitTest logic
//List to store all the elements under the cursor
private List<DependencyObject> hitResultsList = new List<DependencyObject>();
private void HandleMouseDown(object sender, MouseButtonEventArgs e)
{
Point pt = e.GetPosition((UIElement)sender);
hitResultsList.Clear();
//Retrieving all the elements under the cursor
VisualTreeHelper.HitTest(this, null,
new HitTestResultCallback(MyHitTestResult),
new PointHitTestParameters(pt));
//Testing if the grdFilters is under the cursor
if (!hitResultsList.Contains(this.grdFilters) && grdFilters.Visibility == System.Windows.Visibility.Visible)
{
grdFilters.Visibility = System.Windows.Visibility.Hidden;
}
}
//Necessary callback function
private HitTestResultBehavior MyHitTestResult(HitTestResult result)
{
hitResultsList.Add(result.VisualHit);
return HitTestResultBehavior.Continue;
}
that way you can also remove the Mouse.Capture call from the btnFilters_Click:
private void btnFilters_Click(object sender, RoutedEventArgs e)
{
if (grdFilters.Visibility != System.Windows.Visibility.Visible)
grdFilters.Visibility = System.Windows.Visibility.Visible; }
}
Okay, I have tried to use a popup to get this to work but there are a ton of reasons why that doesn't appear to be a route I want to take...especially because I've spent the last two hours trying to get it to work and I've deemed it unholier than all hell (this is despite the fact that I have popups in other places in the app that work just fine, but I digress...)
Basically I need only one piece of functionality that doesn't appear to be standard out of the box in WPF...I have to determine when someone clicks on something OTHER than a known UI element (I.E. they click away from something to close it...much like a popup set to StaysOpen = false)
From what I have gathered this is quite an arduous task and I can't seem to find a straight answer on the best way to do this...any ideas SO?
EDIT:
One of the commenters wanted me to post some sample code and re-reading through my question I really don't want to post something that is unrelated (the XY problem). I am posting this question for two reasons:
The onmouseleave event gets fired as soon as the popup opens. This means that if the popup is set to 'StaysOpen="False"' that the popup appears and immediately disappears no matter what. I believe wholeheartedly that this will not be an issue if I create a component that appears using the Visibility attribute to appear and disappear rather than placing it in a popup. The only reason I considered the popup component was because of it's StaysOpen=False functionality, not because it needs to float above everything else
The popup itself feels quite hacky, especially because it needs to fit inside of a parent component in the visual tree. As you can see from the code below, I have gotten the popup to fit inside of it's parent...but I really don't like binding a component's width and height to another component's actual width and height. This is the second reason I would like to avoid using a popup.
As a result, while this question could be 'how can I get the popup to work', the original question still stands: "How can I listen for a on click away event?" I would like to create a component that fits in the visual tree logically, and behaves as the following:
On hover over a component, appear
On leave a component disappear
On click on a component persist appearing
On click away from a component or itself close
I have all of the above handled except for on click away
How about the UIElement.LostFocus-Event? That seems to be the one you need.
I think in this case, you can be useful routed events. There are two types of events: Bubbling, Direct and Tunneling. Attention should be paid to Bubbling and Tunneling. Bubbling events rises up the logical tree and tunneling down. Below is a diagram from here:
So that event up / down the tree, it should be set on each control. Usually, the demonstration bubbling events, apply this example:
XAML
<Window x:Class="DemoRoutedEvents.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" MouseUp="somethingClicked">
<Grid MouseUp="somethingClicked">
<StackPanel MouseUp="somethingClicked" Margin="0,0,10,0">
<Label x:Name="btnClickMe" Content="Click Me!" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Margin="101,22,0,0" MouseUp="somethingClicked"/>
<CheckBox x:Name="chkhandle" Content="CheckBox" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="241,28,0,0" RenderTransformOrigin="-0.588,1.188"/>
<ListBox x:Name="lstEvents" HorizontalAlignment="Left" Height="604" VerticalAlignment="Top" Width="416" Margin="29,66,0,0"/>
</StackPanel>
</Grid>
</Window>
Code behind
public int eventCounter = 0;
private void somethingClicked(object sender, RoutedEventArgs e)
{
eventCounter++;
String message = "#" + eventCounter.ToString() + ":\r\n" +
" Sender: " + sender.ToString() + ":\r\n" +
" Source: " + e.Source + ":\r\n" +
" Original Source: " + e.OriginalSource;
lstEvents.Items.Add(message);
e.Handled = (bool)chkhandle.IsChecked;
if (e.Handled)
lstEvents.Items.Add("Completed");
}
Output
I tried to optimize this process for multiple panels and components. I have created a attached dependency property IsDebugEvent, which is in the class of EventBehaviours. The principle is simple, we take an event handler and set it for all elements of the type Control (almost all the UIElements it inherits). For panels such as a Grid, StackPanel, WrapPanel, etc, Panel is the base class.
In the handler, we find ListBox and display the name of the panel s the element that caused the event, just for test. The example uses the event PreviewMouseLeftButtonDown (tunneling), because the first fires is an event at the Button.Click, and when it works, it conflicts with the event MouseUp. Quote from here:
ButtonBase inherits from UIElement, a Button will also have access to all of the mouse button events defined for UIElement. Because the Button does something in response to button presses, it swallows the bubbling events (e.g. MouseLeftButtonDown and MouseDown). You can still detect these lower level button press events by adding handlers for the tunneling events (e.g. PreviewMouseLeftButtonDown and PreviewMouseDown).
XAML
<Window x:Class="AwayEventHelp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:AwayEventHelp"
Title="MainWindow" Height="550" Width="525"
WindowStartupLocation="CenterScreen">
<Grid>
<CheckBox Name="DebugCheckBox" Width="100" Height="30"
VerticalAlignment="Top"
Content="Debug event" IsChecked="False"
Checked="DebugCheckBox_Checked" Unchecked="DebugCheckBox_Unchecked" />
<StackPanel Name="LeftStackPanel" Width="150" local:EventBehaviours.IsDebugEvent="False"
HorizontalAlignment="Left" Background="BlanchedAlmond">
<Button Name="LeftButton1" Height="30" Content="LeftButton1" />
<Button Name="LeftButton2" Height="30" Content="LeftButton2" />
<Button Name="LeftButton3" Height="30" Content="LeftButton3" />
<Label Name="JustLabelLeft" Content="JustLabelLeft" Background="Azure" HorizontalContentAlignment="Center" />
</StackPanel>
<StackPanel Name="RightStackPanel" Width="150" local:EventBehaviours.IsDebugEvent="False"
HorizontalAlignment="Right" Background="Azure">
<Button Name="RightButton1" Height="30" Content="RightButton1" />
<Button Name="RightButton2" Height="30" Content="RightButton2" />
<Button Name="RightButton3" Height="30" Content="RightButton3" />
<Label Name="JustLabelRight" Content="JustLabelRight" Background="BlanchedAlmond" HorizontalContentAlignment="Center" />
</StackPanel>
<Grid Name="GridPanel" Width="100" Height="100" local:EventBehaviours.IsDebugEvent="False"
VerticalAlignment="Bottom" Background="CadetBlue">
<Label Name="LabelInGrid" Width="100" Height="50" Content="LabelInGrid" Background="AliceBlue" />
</Grid>
<ListBox Name="EventOutput" Width="180" Height="180" Background="AliceBlue" />
</Grid>
</Window>
Code behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void DebugCheckBox_Checked(object sender, RoutedEventArgs e)
{
EventBehaviours.SetIsDebugEvent(LeftStackPanel, true);
EventBehaviours.SetIsDebugEvent(RightStackPanel, true);
EventBehaviours.SetIsDebugEvent(GridPanel, true);
}
private void DebugCheckBox_Unchecked(object sender, RoutedEventArgs e)
{
EventBehaviours.SetIsDebugEvent(LeftStackPanel, false);
EventBehaviours.SetIsDebugEvent(RightStackPanel, false);
EventBehaviours.SetIsDebugEvent(GridPanel, false);
}
}
public class EventBehaviours : DependencyObject
{
#region IsDebugEvent declaration
public static void SetIsDebugEvent(DependencyObject target, bool value)
{
target.SetValue(IsDebugEventProperty, value);
}
public static bool GetIsDebugEvent(DependencyObject DepObject)
{
return (bool)DepObject.GetValue(IsDebugEventProperty);
}
public static readonly DependencyProperty IsDebugEventProperty =
DependencyProperty.RegisterAttached("IsDebugEvent",
typeof(bool),
typeof(EventBehaviours),
new UIPropertyMetadata(false, OnIsDebugEvent));
#endregion
private static void OnIsDebugEvent(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
Panel MyPanel = sender as Panel;
if (e.NewValue is bool && ((bool)e.NewValue == true))
{
MyPanel.PreviewMouseLeftButtonDown += new MouseButtonEventHandler(MyControl_PreviewMouseLeftButtonDown);
if (MyPanel.Children.Count != 0)
{
foreach (Control MyControl in MyPanel.Children)
{
MyControl.PreviewMouseLeftButtonDown += new MouseButtonEventHandler(MyControl_PreviewMouseLeftButtonDown);
}
}
}
else
{
foreach (Control MyControl in MyPanel.Children)
{
MyControl.PreviewMouseLeftButtonDown -= new MouseButtonEventHandler(MyControl_PreviewMouseLeftButtonDown);
}
MyPanel.PreviewMouseLeftButtonDown -= new MouseButtonEventHandler(MyControl_PreviewMouseLeftButtonDown);
}
}
/// <summary>
/// Main handler of PreviewMouseLeftButtonDown event
/// </summary>
private static void MyControl_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
string OutInfo = string.Empty;
if (sender.GetType() == typeof(StackPanel))
{
StackPanel MyStackPanel = sender as StackPanel;
Grid MyGrid = MyStackPanel.Parent as Grid;
OutInfo = "PanelName: " + MyStackPanel.Name;
OutInfoInListBox(MyGrid, OutInfo);
}
else if (sender.GetType() == typeof(Grid))
{
Grid MyGrid = sender as Grid;
Grid MyMainGrid = MyGrid.Parent as Grid;
OutInfo = "PanelName: " + MyGrid.Name;
OutInfoInListBox(MyMainGrid, OutInfo);
}
else
{
Control MyControl = sender as Control;
Panel MyStackPanel = MyControl.Parent as Panel;
Grid MyGrid = MyStackPanel.Parent as Grid;
OutInfo = "ControlName: " + MyControl.Name;
OutInfoInListBox(MyGrid, OutInfo);
}
}
/// <summary>
/// Get ListBox and insert some info
/// </summary>
/// <param name="ParentGrid">Panel, where locate ListBox</param>
/// <param name="info">Just string</param>
private static void OutInfoInListBox(Grid ParentGrid, string info)
{
ListBox MyEventOutput = ParentGrid.FindName("EventOutput") as ListBox;
MyEventOutput.Items.Add(info);
}
}
Output
By clicking on the CheckBox, set a dependency property IsDebugEvent in True, subject thus causing OnIsDebugEvent, where we set the handlers. If you deselect the CheckBox in, then all event handlers deleted.
To set the events immediately on startup, you need to make sure that all the items on the successfully booted. This can be done in the event ContentRendered of Window.
I have the following XAML code as an example in my WPF application
<StackPanel Height="23" Name="MSpanel" Orientation="Horizontal" Width="138" Margin="37,13,0,0" VerticalAlignment="Top" HorizontalAlignment="Left" >
<TextBox Height="23" Name="MTBox" Width="120" Text="0" />
<ScrollBar Height="23" Name="MSBar" Width="18" TouchUp="SBar_TouchUp" />
</StackPanel>
<StackPanel Height="23" Name="CSPanel" Orientation="Horizontal" Width="138" Margin="37,41,0,0" VerticalAlignment="Top" HorizontalAlignment="Left">
<TextBox Height="23" Name="CTBox" Width="120" Text="0" />
<ScrollBar Height="23" Name="CSBar" Width="18" TouchUp="SBar_TouchUp" />
</StackPanel>
I have this function:
private void SBar_TouchUp(object sender, TouchEventArgs e)
{
//what goes here?
//siblings.getFirst('textbox').text += 1;
}
What I was hoping to do, is have 1 function that controls these "Psudo" numeric up downs in WPF. If there was some way to have a unified function that could, reference the sibling textbox, so I only have to write it once. That would be ideal.
I'm very familiar with jQuery, and XAML looks like an HTML DOM, ... Is there a way to browse the tree?
I realize there are existing Numeric Up Downs available to download. This idea I believe would be good to know for the future in other endeavors as well. Thanks.
The solution that worked!
private void SBar_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
if (e.NewValue == 0) return; //abort here, no change
ScrollBar sb = (ScrollBar)sender;
StackPanel sp = (StackPanel)sb.Parent;
TextBox tb = (TextBox)sp.Children[0];
int change = e.NewValue < 0 ? 1 : -1;
sb.Value = 0; //this will invoke this function again
tb.Text = (Convert.ToInt32(tb.Text) + change).ToString();
}
Each element in the visual tree has a Parent and VisualParent property - as all elements are based on UIElement - either should give you the parent object.
In this case the parent of the ScrollBar is the StackPanel. You can then use the Children property of the StackPanel to get the collection of child objects. You know which is the ScrollBar (it's the sender) so the other must be the TextBox.
You can do something like this:
private void SBar_TouchUp(object sender, TouchEventArgs e)
{
//siblings.getFirst('textbox').text += 1;
var siblings = ((sender as FrameworkElement).Parent as Panel).Children;
var textbox = siblings.OfType<TextBox>().First();
textbox.Text = (int.Parse(textbox.Text) + 1).ToString();
}
but I would suspect that there are probably better ways to do what you want, like data binding or naming elements in attached properties.
Yeah, there are many properties for both Logical and Visual trees.
Like FrameworkElement.Parent or Panel.Children.
I don't think there is directly method to get sibling, but its not that hard to get index in list of children of parent and getting next item.