WPF, sibling selection like jQuery? - c#

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.

Related

How To Let A Grid Capture The Mouse, But Still Allow It's Children To Handle Click Events

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; }
}

C# Events in a tic tac toe game

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.

WPF: Easiest way to add control to grid based on ListView selection

I have a ListView which contains items that should represent certain settings, which can be changed by the user. I am looking for the easiest way to link the ListViewItem to a custom user control.
What I have now:
<ListView
Grid.Row="0"
Grid.Column="0"
SelectionChanged="SettingsListViewSelectionChanged">
<ListViewItem x:Name="PathSettings" Content="Path"/>
<ListViewItem x:Name="HideShowTvShows" Content="Hide/Show TV Shows"/>
</ListView>
And then in the code behind I figure out which item was clicked, and attach the corresponding UserControl to the Grid.
private void SettingsListViewSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var listView = e.Source as ListView;
if (listView != null)
{
SettingsContentPanel.Children.Clear();
if (listView.SelectedItem.Equals(_pathSettings))
{
SettingsContentPanel.Children.Add(_pathSettings);
_pathSettings.SetValue(Grid.RowProperty, 0);
_pathSettings.SetValue(Grid.ColumnProperty, 1);
}
if (listView.SelectedItem.Equals(_hideShowTvShowsSettings))
{
SettingsContentPanel.Children.Add(_hideShowTvShowsSettings);
_hideShowTvShowsSettings.SetValue(Grid.RowProperty, 0);
_hideShowTvShowsSettings.SetValue(Grid.ColumnProperty, 1);
}
}
}
And the Grid itself:
<Grid
x:Name="SettingsContentPanel"
Grid.Row="0"
Grid.Column="2"
Grid.ColumnSpan="2" />
Is there a way to get rid of the boiler plate code behind and use XAML for this purpose?
Looks like you're looking for a ContentPresenter:
<ContentPresenter x:Name="SettingsContentPanel"
Content="{Binding SelectedItem, ElementName=MyLstView}"/>

Finding a control inside another control in WPF

Since there is no link button in WPF I created a link button using hyperlink and text block controls.
There are 3 controls:
<TextBlock Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Left" >
<Hyperlink Name="hyplnkIsActiveMarkets" Click="hyplnkIsActive_Click" Foreground="Blue" >
<TextBlock Name="txtblkIsActiveMarkets" Text="Active" />
</Hyperlink>
</TextBlock>
<TextBlock Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Left">
<Hyperlink Name="hyplnkIsActiveBudgets" Click="hyplnkIsActive_Click" Foreground="Blue" >
<TextBlock Name="txtblkIsActiveBudgets" Text="Active" />
</Hyperlink>
</TextBlock>
<TextBlock Grid.Column="2" VerticalAlignment="Center" HorizontalAlignment="Left">
<Hyperlink Name="hyplnkIsActivePrograms" Click="hyplnkIsActive_Click" Foreground="Blue" >
<TextBlock Name="txtblkIsActivePrograms" Text="Active" />
</Hyperlink>
</TextBlock>
All the link buttons calls same click method
private void hyplnkIsActive_Click(object sender, RoutedEventArgs e)
{
Hyperlink objHyperlink = (Hyperlink)sender;
TextBlock objTextBlock = new TextBlock();
if (objHyperlink == hyplnkIsActiveMarkets)
{
objTextBlock = txtblkIsActiveMarkets;
}
else if (objHyperlink == hyplnkIsActiveBudgets)
{
objTextBlock = txtblkIsActiveBudgets;
}
else if (objHyperlink == hyplnkIsActivePrograms)
{
objTextBlock = txtblkIsActivePrograms;
}
if (objTextBlock.Text == "Active")
ChangeHyperLinkStatus(objHyperlink, objTextBlock, Status.Inactive);
else ChangeHyperLinkStatus(objHyperlink, objTextBlock, Status.Active);
}
In the click method I check for the text block inside the hyper link individually using if condition.
Is there any easier way to do this? That's basically finding control inside a control?
UPDATE: you can not use VisualTreeHelper.GetParent(...) to get the parent of your hyperlink as you have mentioned hyperlink is not visual. corrected the answer.
See code below.
private void hyplnkIsActive_Click(object sender, RoutedEventArgs e)
{
Hyperlink objHyperlink = (Hyperlink)sender;
TextBlock objTextBlock = (TextBlock)LogicalTreeHelper.GetChildren(objHyperlink)[0];
// This will give logical tree first child of objHyperlink
if (objTextBlock.Text == "Active")
ChangeHyperLinkStatus(objHyperlink, objTextBlock, Status.Inactive);
else
ChangeHyperLinkStatus(objHyperlink, objTextBlock, Status.Active);
}
See this article about logical tree on MSDN
I think you're going into the wrong direction by relaying your execution logic on controls and not on data.
You can, for example, bind a ICommand or RelayCommand to the buttons, or just subscribe different events, or define a custom DataTemplate where on mouse down the clicked control is assignable to some ModelView property.
Doing in way you do, you create tough coupling between UI and your execution logic.
In this case easier use WindowsForm then WPF.
I got it finally . Thanks to Maheep fa his help
TextBlock objTextBlock = (TextBlock)LogicalTreeHelper.GetChildren(objHyperlink).Cast<System.Windows.Documents.InlineUIContainer>().FirstOrDefault().Child;

Calling a base-class method from Silverlight XAML

As part of learning Silverlight, I'm trying to create a base UserControl to use as the starting point for my inherited controls.
It's very simple, it merely defines some callback methods:
public class ClickableUserControl : UserControl
{
private Control _superParent;
public ClickableUserControl()
{
}
public ClickableUserControl(Control superParent)
{
_superParent = superParent;
this.MouseEnter += new MouseEventHandler(PostfixedLayoutItem_MouseEnter);
this.MouseLeave += new MouseEventHandler(PostfixedLayoutItem_MouseLeave);
this.MouseLeftButtonDown += new MouseButtonEventHandler(PostfixedLayoutItem_MouseLeftButtonDown);
}
public virtual void PostfixedLayoutItem_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var elements = VisualTreeHelper.FindElementsInHostCoordinates(e.GetPosition(null), this);
if (elements.Any(elm => elm is ClickToEditTextBox))
{
e.Handled = false;
}
}
public void PostfixedLayoutItem_MouseLeave(object sender, MouseEventArgs e)
{
this.Cursor = Cursors.Arrow;
}
public void PostfixedLayoutItem_MouseEnter(object sender, MouseEventArgs e)
{
this.Cursor = Cursors.Hand;
}
public void ClickToEditTextBox_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter || e.Key == Key.Escape)
{
VisualStateManager.GoToState((Control)sender, "NotEdit", false);
_superParent.Focus();
}
}
}
Please note the ClickToEditTextBox_KeyDown() method which is the problem!
Now, I have an inherited control that looks as follows (CheckboxLayoutItem.xaml):
<local:ClickableUserControl x:Class="OpusFormBuilder.LayoutItems.CheckboxLayoutItem"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:OpusFormBuilder.LayoutItems"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400" x:Name="LayoutItem">
<StackPanel Name="stackPanel1" Orientation="Horizontal">
<lc:LayoutItem Label="layoutItem" Name="layoutItem">
<lc:LayoutItem.LabelTemplate>
<DataTemplate>
<Self:ClickToEditTextBox KeyDown="ClickToEditTextBox_KeyDown" Text="{Binding Label, Mode=TwoWay, ElementName=layoutItem}" MaxWidth="150" MinWidth="150" TextWrapping="Wrap" MaxHeight="200" VerticalAlignment="Top" />
</DataTemplate>
</lc:LayoutItem.LabelTemplate>
<dxe:CheckEdit Name="InnerCheckbox" Grid.ColumnSpan="2" Grid.Column="0" Grid.Row="0" VerticalAlignment="Top" HorizontalAlignment="Stretch" IsEnabled="False" />
</lc:LayoutItem>
<Self:ClickToEditTextBox KeyDown="ClickToEditTextBox_KeyDown" x:Name="Description" MaxWidth="150" MaxHeight="200" TextWrapping="Wrap" VerticalAlignment="Top" HorizontalAlignment="Right" />
</StackPanel>
(Note - I have removed some namespace declarations for easier reading)
Note the following line:
<Self:ClickToEditTextBox KeyDown="ClickToEditTextBox_KeyDown" Text="{Binding Label, Mode=TwoWay, ElementName=layoutItem}" MaxWidth="150" MinWidth="150" TextWrapping="Wrap" MaxHeight="200" VerticalAlignment="Top" />
in which I set the KeyDown-event on a ClickToEditTextBox (the self namespace is defined, and correctly so).
Now, in the code behind (CheckboxLayoutItem.xaml.cs) in the constructor the call to InitializeComponent() fails with the error: Failed to assign to property 'System.Windows.UIElement.KeyDown'. [Line: 17 Position: 42]
I can't debug into InitializeComponent, however, but I can't see what could possibly be the issue from this error, other than the KeyDown events in the XAML.
Now, here is my question - how come I (seemingly) cannot reference a method defined in my base-class!? Previously I had the method in the CheckboxLayoutItem.xaml.cs method itself, but as some other controls needed some of the same functionality, it seemd a better option to put it in a base class.
Cheers!
I know this doesn't really answer your question, but you might want to look at Template (Custom Controls) controls. UserControl really isn't the best solution for what you're trying to do here.
UserControls are best for situations where you're building a one-off control that you don't intend in inheriting from.

Categories