Avalonia button click event is not working - c#

I have a simple Avalonia form:
<Window xmlns="https://github.com/avaloniaui"
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"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="AvaloniaExperiment.MainWindow"
Title="AvaloniaExperiment">
<StackPanel>
<TextBlock>Welcome to Avalonia!</TextBlock>
<Button Name="btn" Click="btn_OnClick">Fred!</Button>
</StackPanel>
</Window>
And a method in the code behind (I want to do things this way until I become familiar with Avalonia, then maybe I'll try MVVM):
private void btn_OnClick()
{
btn.Text = "Ginger";
}
However I get these compile errors:
The name btn does not exist in the current context (in the code behind)
Unable to find suitable setter or adder for property Click of type Avalonia.Controls:Avalonia.Controls.Button for argument System.Private.CoreLib:System.String, available setter parameter lists are:
System.EventHandler`1[[Avalonia.Interactivity.RoutedEventArgs, Avalonia.Interactivity, Version=0.9.0.0, Culture=neutral, PublicKeyToken=null]] (in the XAML)
Unable to find suitable setter or adder for property Command of type Avalonia.Controls:Avalonia.Controls.Button for argument System.Runtime:System.String, available setter parameter lists are:
Avalonia.UnsetValueType
Avalonia.Data.IBinding
System.Windows.Input.ICommand (also in the XAML)
What could I be doing wrong in hooking up this event handler?

The sender is the button you just clicked, so typecast sender to Button and set its Content property (not Text) to whatever you want to.
public void btn_OnClick( object? sender, RoutedEventArgs args )
{
( sender as Button )!.Content = "Ginger";
}
No need to look it up in the tree or anything else, this way you can reuse the same code behind for all your buttons, and for instance, depending on which button it is, set different names or styles, or other properties, etc.
More advanced:
public void btn_OnClick( object? sender, RoutedEventArgs args )
{
var button = ( sender as Button )!;
switch ( button.Name )
{
case "btn":
{
button.Content = "Ginger";
}
break;
case "otherBtn":
{
button.Content = "Ale";
}
break;
default:
{
button.Content = "No clue which Button you are!";
}
break;
}
}

have you tried...
public void btn_OnClick(object sender, RoutedEventArgs e)
{
btn.Text = "Ginger";
}

You should add a ControlLink in the Parent Control Constructor
like this:
public class AnyParentControl
{
Button btn; // for the class
public AnyParentControl() // constructor
{
InitializeComponent(); // necessary method for Avalonia
btn = this.Find<Button>("The Control Name on XAML File");
btn.Click += Cbtn_Click; // event link
}
}
Greetings from Peru :D

Button does not have Text property. It does have Content.
btn.Content = "Ginger";

Related

Issue with making an hangman game in UWP

I'm trying to create an hangman game in UWP and I'm not quite sure where to type the button click event of each letter in the code in order to have it recognize all of the variables in MainPage without affecting functionality.
If possible to have the button clicks in a separate class, even better.
Would appreciate if you could help me, thanks in advance!
namespace Hangman
{
public sealed partial class MainPage : Page
{
int _currentIndex;
string _currentWord;
string[] _strArr = { "ant", "bee", "spider", "mosquito" };
int _difficulty = 1;
public MainPage()
{
this.InitializeComponent();
Random rnd = new Random();
_currentIndex = rnd.Next(0, 4);
_currentWord = _strArr[_currentIndex];
foreach (char c in _currentWord)
{
string _hiddenWord = string.Empty;
foreach (char ch in _currentWord)
{
_hiddenWord += "_" + (char)160;
}
_textBl.Text = _hiddenWord;
}
}
private void a_Click(object sender, RoutedEventArgs e)
{
}
}
}
I want to capture Button clicks of buttons on my XML code. I've made a property like so public char Key { get; set; } and had each Button click insert a different value dependant on the letter, for example: on button_a, Key = 'a';
I probably understand what you mean. You have a lot of buttons (like a button that makes up a keyboard). Each time you click the button, you get the button's identity and then type it into the text box.
You can use the Button.Tag property to record your key, like this:
<Button Tag="a" Content="A" Click="Button_Click" />
In code-behind, the Button_Click method has two parameters, where the sender refers to the Button that triggered the event, so you can convert it and get the Tag property.
private void Button_Click(object sender, RoutedEventArgs e)
{
var btn = sender as Button;
string tag = btn.Tag.ToString();
// Do Something
}
In this way, you can bind all the buttons to the same handler, which is very convenient.
Best regards.
You have to go to your XAML file where you design your UI, find the button and then add
<Button Content="Your Button Text" onClick="a_Click" />
there.

Trying to make a textbox with a simple bit of code but not sure why it gives error

public partial class MainWindow : Window
{
private Rectangle player = new Rectangle();
private int x=0;
private int y = 0;
private System.Windows.Threading.DispatcherTimer timer = new System.Windows.Threading.DispatcherTimer();
public MainWindow()
{
InitializeComponent();
player.Width = 50;
player.Height = 50;
player.Fill = Brushes.Red;
player.MouseEnter += Player_MouseEnter;
player.MouseLeave += Player_MouseLeave;
player.MouseDown += Player_MouseDown;
myCanvas.Children.Add(player);
}
private void Player_MouseDown(object sender, MouseButtonEventArgs e)
{
Random rand = new Random(); //Creates the pseudo-random movement
int a = rand.Next(1, 1001); //With variables a and b
int b = rand.Next(1, 1001);
Canvas.SetLeft(player, a);
Canvas.SetTop(player, b);
}
private void Player_MouseLeave(object sender, MouseEventArgs e)
{
player.Fill = Brushes.Red;
}
private void Player_MouseEnter(object sender, MouseEventArgs e)
{
player.Fill = Brushes.Blue;
}
public void drawPlayer()//makes it easier to redraw after every click
{
Canvas.SetLeft(player, x);
Canvas.SetTop(player, y);
}
private void makeTextBox(//needs something here?)
{
TextBox.Text = "POINTS Counter";//Using this later on
}
}
This textbox code I'm guessing needs something in the makeTextBox() for it to make sense. However I cant figure out what as I'm new to wpf, and c# in general. The code simply creates a square that changes colour if someone hovers over it. It then travels a pseudo-random amount on the x and y axis(between 1 and 1000). I now want to add a timer and points system, but can't make the textbox.
I keep getting the error
CS0120 C# An object reference is required for the non-static field, method, or property 'TextBox.Text'
If anyone could help that would be great!
You never initialized your Textbox
for any object-oriented programming language you can't directly access classes unless they are static.
TextBox is not a static class.
I would highly recommend to add the textbox in xaml, as suggested in the comments.
If you do not want to do this for whatever reasons write
TextBox MyTextbox = new TextBox();
MyTextBox.Text = "POINTS Counter";//Using this later on
and it should work.
edit: however, even though your error is going to vanish, you will not see your textbox by just doing this.
In order to make it visible, you need to make your container object know about it. I guess you are using some sort of grid in your xaml code, which probably looks like this:
<Window x:Class="Mytest.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:pigc="http://schemas.proleit.com/ic/GUI/Charts"
xmlns:pic="http://schemas.proleit.com/ic/Core"
xmlns:local="clr-namespace:Stanlytest"
xmlns:graph="clr-namespace:Graph"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<!- Whatever you do here -->
</Grid>
Now give your Grid a name, if you haven't (like <Grid Name="MainGrid"> </Grid>), then add the textbox as a child to the grid in the xaml.cs (like: MainGrid.Children.Add(MyTextBox).)
I can just advise you again, that although this is a little unfamiliar at first for newcommers (I just recently started learing wpf myself), you should add your objects in xaml.

How do I programmatically add event handlers to RadioButtons in a DataGridTemplateColumn that is dynamically created using XamlReader?

I am trying to add event handlers to RadioButtons in a DataGridTemplateColumn which is created through XamlReader.
Here is the code I'm using:
string templateColumnStart = #"<DataGridTemplateColumn Header='Svar' Width='200' x:Name='myTemplateColumn'
xmlns ='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation='Horizontal'>";
string templateColumnEnd = #"</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>";
string radioButtonString = #"<RadioButton Name='rb1' Content='1' IsChecked='false'/>";
string fullXamlString = templateColumnStart + radioButtonString + templateColumnEnd;
MemoryStream stream = new MemoryStream(ASCIIEncoding.UTF8.GetBytes(fullXamlString));
DataGridTemplateColumn templateColumn = (DataGridTemplateColumn)XamlReader.Load(stream);
StackPanel template = (StackPanel)templateColumn.CellTemplate.LoadContent();
RadioButton radiobutton = (RadioButton)template.FindName("rb1");
radiobutton.Checked += new RoutedEventHandler(rb_Checked);
myDataGrid.Columns.Add(templateColumn);
The reason I'm using XamlReader to create the TemplateColumn is that the number of RadioButtons needed in the column will vary, and as such, I need the ability to dynamically change the number of RadioButtons created.
The column is created and added to the DataGrid without a problem, and the RadioButton is displayed correctly. However, the event handler does not seem to have been added, as checking the button does not trigger it.
As a side note, simply adding "Checked='rb_Checked'" to the radioButtonString throws a XamlParserException, as XamlReader is not able to handle event handlers.
Any help with this would be greatly appreciated.
The main issue here is that you are hooking up the event handler to the wrong RadioButton instance. A new one will be created by the WPF runtime when the CellTemplate is applied so calling the LoadContent() method is meaningless here.
Unfortunately the DataGridColumn class doesn't raise any events when the GenerateElement method is invoked so you will have to create your own custom DataGridColumn to be able to get a reference to the StackPanel once the template has actually been applied. Before this there is no StackPanel nor RadioButton. A DataTemplate is as the name implies just a template that is being applied eventually.
Here is what you could do if you are not satisfied with using an implicit Style that gets applied to all RadioButtons.
Create a custom class that inherits from DataGridTemplateColumn and raises an event when the GenerateElement method is called:
namespace WpfApplication2
{
public class CellElementGeneratedEventArgs : EventArgs
{
public FrameworkElement Content { get; set; }
}
public delegate void CellElementGeneratedEventHandler(object sender, CellElementGeneratedEventArgs e);
public class MyDataGridTemplateColumn : DataGridTemplateColumn
{
public event CellElementGeneratedEventHandler ElementGenerated;
protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
{
FrameworkElement fe = base.GenerateElement(cell, dataItem);
if(fe != null)
fe.Loaded += Fe_Loaded;
return fe;
}
private void Fe_Loaded(object sender, RoutedEventArgs e)
{
if (ElementGenerated != null)
ElementGenerated(this, new CellElementGeneratedEventArgs() { Content = sender as FrameworkElement });
}
}
}
And modify your XAML markup and code slightly:
string templateColumnStart = #"<local:MyDataGridTemplateColumn Header='Svar' Width='200' x:Name='myTemplateColumn' xmlns ='http://schemas.microsoft.com/winfx/2006/xaml/presentation' xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' xmlns:local='clr-namespace:WpfApplication2;assembly=WpfApplication2'> <DataGridTemplateColumn.CellTemplate><DataTemplate><StackPanel Orientation='Horizontal'>";
string templateColumnEnd = #"</StackPanel></DataTemplate></DataGridTemplateColumn.CellTemplate></local:MyDataGridTemplateColumn>";
string radioButtonString = #"<RadioButton Name='rb1' Content='1' IsChecked='false'/>";
string fullXamlString = templateColumnStart + radioButtonString + templateColumnEnd;
using (MemoryStream stream = new MemoryStream(ASCIIEncoding.UTF8.GetBytes(fullXamlString)))
{
MyDataGridTemplateColumn templateColumn = (MyDataGridTemplateColumn)XamlReader.Load(stream);
templateColumn.ElementGenerated += (ss, ee) =>
{
ContentPresenter cc = ee.Content as ContentPresenter;
if(cc != null)
{
StackPanel sp = VisualTreeHelper.GetChild(cc, 0) as StackPanel;
if(sp != null)
{
RadioButton rb = sp.FindName("rb1") as RadioButton;
if (rb != null)
rb.Checked += rb_Checked;
}
}
};
myDataGrid.Columns.Add(templateColumn);
}
Note that the you will have to change "assembly=WpfApplication2" to the name of the assembly where you define your MyDataGridTemplateColumn class. The same applies to the namespace.
You can add EventSetter to your window like this:
<Window.Resources>
<Style TargetType="{x:Type RadioButton}">
<EventSetter Event="Checked" Handler="RadioButton_Checked"/>
</Style>
</Window.Resources>
Event handler method:
void RadioButton_Checked(object sender, RoutedEventArgs e)
{
// Handle the event...
}
This will call the handler for every RadioButton in your DataGrid.
If I remember correctly, you can add Checked="CheckHandlerFunction" in the XAML itself. That may work.
This may not apply in your situation, but have you looked into templates and binding controls to DataContext? You can automate a lot of control creation in WPF using them.

Update ThemeResources from c#?

I've got a void that analyses an image, extracts two dominant colors, and replaces "SystemControlForegroundAccentBrush" and "SystemControlHighlightAccentBrush", that I'm overriding in App.xaml. It works well, except that it takes a bit of time to analyse the image, so it changes the colors once all the controls in my page have already loaded.
As a result, they stay the old accent color. How can I get them to bind dynamically to that ThemeRessource?
Here's my app.xaml:
<Application.Resources>
<ResourceDictionary x:Name="resdic">
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Dark">
<SolidColorBrush x:Key="SystemControlForegroundAccentBrush"/>
<SolidColorBrush x:Key="SystemControlHighlightAccentBrush"/>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>
</Application.Resources>
This is the (very simplified) part of the ColorExtractor.cs class that changes the colors:
public async static void Analyse()
{
Application.Current.Resources["SystemControlForegroundAccentBrush"] = new SolidColorBrush(color);
Application.Current.Resources["SystemControlHighlightAccentBrush"] = new SolidColorBrush(color2);
}
And I've got a bunch of controls in Page.xaml that have their Foreground set as such:
<TextBlock Foreground="{ThemeResource SystemControlForegroundAccentBrush]"/>
I call ColorExtractor.Analyse() in my Page.xaml.cs (at the OnNavigatedTo event). I can always create an event that gets fired once the colors are set, but I need to find a way to update the colors of all the controls in my page once that's done.
You can have a look at how Template 10 does theming changes, but in their case they are defining two different themes in resource dictionaries in advance. You can find their code in the repo on Github, but here are some of the code used:
(Window.Current.Content as FrameworkElement).RequestedTheme = value.ToElementTheme();
Views.Shell.SetRequestedTheme(value, UseBackgroundChecked);
From here
public static void SetRequestedTheme(ApplicationTheme theme, bool UseBackgroundChecked)
{
WindowWrapper.Current().Dispatcher.Dispatch(() =>
{
(Window.Current.Content as FrameworkElement).RequestedTheme = theme.ToElementTheme();
ParseStyleforThemes(theme);
HamburgerMenu.NavButtonCheckedForeground = NavButtonCheckedForegroundBrush;
HamburgerMenu.NavButtonCheckedBackground = (UseBackgroundChecked) ?
NavButtonCheckedBackgroundBrush : NavButtonBackgroundBrush;
HamburgerMenu.NavButtonCheckedIndicatorBrush = (UseBackgroundChecked) ?
Colors.Transparent.ToSolidColorBrush() : NavButtonCheckedIndicatorBrush;
HamburgerMenu.SecondarySeparator = SecondarySeparatorBrush;
List<HamburgerButtonInfo> NavButtons = HamburgerMenu.PrimaryButtons.ToList();
NavButtons.InsertRange(NavButtons.Count, HamburgerMenu.SecondaryButtons.ToList());
List<HamburgerMenu.InfoElement> LoadedNavButtons = new List<HamburgerMenu.InfoElement>();
foreach (var hbi in NavButtons)
{
StackPanel sp = hbi.Content as StackPanel;
if (hbi.ButtonType == HamburgerButtonInfo.ButtonTypes.Literal) continue;
ToggleButton tBtn = sp.Parent as ToggleButton;
Button btn = sp.Parent as Button;
if (tBtn != null)
{
var button = new HamburgerMenu.InfoElement(tBtn);
LoadedNavButtons.Add(button);
}
else if (btn != null)
{
var button = new HamburgerMenu.InfoElement(btn);
LoadedNavButtons.Add(button);
continue;
}
else
{
continue;
}
Rectangle indicator = tBtn.FirstChild<Rectangle>();
indicator.Visibility = ((!hbi.IsChecked ?? false)) ? Visibility.Collapsed : Visibility.Visible;
if (!hbi.IsChecked ?? false) continue;
ContentPresenter cp = tBtn.FirstAncestor<ContentPresenter>();
cp.Background = NavButtonCheckedBackgroundBrush;
cp.Foreground = NavButtonCheckedForegroundBrush;
}
LoadedNavButtons.ForEach(x => x.RefreshVisualState());
});
}
From here
I've got a void that analyses an image, extracts two dominant colors, and replaces "SystemControlForegroundAccentBrush" and "SystemControlHighlightAccentBrush", that I'm overriding in App.xaml.
First I don't think your code in app.xaml can override the ThemeResource, and you used this Brush in the Page like this:
<TextBlock Foreground="{ThemeResource SystemControlForegroundAccentBrush}" Text="Hello World!" FontSize="50" />
If you press "F12" on the SystemControlForegroundAccentBrush, you can find this resource actually in the "generic.xaml" file.
Now suppose your ColorExtractor.cs class works fine and ColorExtractor.Analyse() can override the color of those two brushes, and you have many controls in the page uses these two resources, refreshing the Page here can solve your problem.
But I think it's better that not put this operation in OnNavagateTo event or Page.Loaded event, there is no refresh method in UWP, we use navigating again to this page to refresh, so if putting this operation in OnNavagateTo event or Page.Loaded event, every time you navigate to this page, the resources will be overrided and it will navigate again. So I put this operation in a Button click event like this:
public bool Reload()
{
return Reload(null);
}
private bool Reload(object param)
{
Type type = this.Frame.CurrentSourcePageType;
if (this.Frame.BackStack.Any())
{
type = this.Frame.BackStack.Last().SourcePageType;
param = this.Frame.BackStack.Last().Parameter;
}
try { return this.Frame.Navigate(type, param); }
finally
{
this.Frame.BackStack.Remove(this.Frame.BackStack.Last());
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
ColorExtractor.Analyse();
Reload();
}
In the end, I decided to create an event in ColorExtractor.cs :
public static event EventHandler Analysed;
public async static void Analyse(BitmapImage poster)
{
//Analyse colors
Analysed(null, null);
}
And then, on my MainPage.xaml.cs:
ColorExtractor.Analyse(bmp);
ColorExtractor.Analysed += (sender, EventArgs) =>
{
//Set Page foreground color, as a lot of controls are dynamically binded to their parent's foreground brush.
//If a control isn't automatically binded, all I have to do is say: Foreground="{Binding Foreground, ElementName=page}"
page.Foreground = Application.Current.Resources["SystemControlForegroundAccentBrush"] as SolidColorBrush;
page.BorderBrush = Application.Current.Resources["SystemControlHighlightAccentBrush"] as SolidColorBrush;
//Reload any custom user control that sets it's children's color when it's loaded.
backdrop.UserControl_Loaded(null, null);
};
So I'm not actually binding my controls to the ForegroundAccentBrush directly, but this works without needing to re-navigate to the page.

Selecting A Series Of Text Boxes

I have a series(10) of selectable TextBoxes. I need to be able to click on one of them and select all the text boxes on which the mouse is moved until the click is released.
I used the following code but I am unable to hit the MouseMove on the other TextBoxes. It always hits the TextBox on which the Click was made.
class SelectableTextBox: TextBox
{
public Boolean IsSelected { get; protected set; }
public void select(Boolean value)
{
this.IsSelected = value;
if (value)
{
this.Background = System.Windows.Media.Brushes.Aqua;
}
else
{
this.Background = System.Windows.Media.Brushes.White;
}
}
}
private void onPreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
SelectableTextBox textBox = (SelectableTextBox)sender;
this.SelectionStartedRight = !textBox.IsSelected;
textBox.select(!textBox.IsSelected);
}
private void onPreviewMouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
SelectableTextBox textBox = (SelectableTextBox)sender;
if (this.SelectionStartedRight)
{
textBox.select(true);
}
}
private void onPreviewMouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
SelectableTextBox textBox = (SelectableTextBox)sender;
this.SelectionStartedRight = false;
}
Try using MouseEnter event instead of MouseMove. Attach MouseEnter to your selectable textboxes. That would ensure that only they trigger the desired event handler and its code.
If you decide to stay with the global handler, be careful when you convert sender to a specific control type. You need to account for those times when it's not the "expected" control:
SelectableTextBox textBox = sender as SelectableTextBox;
if (textBox != null)
{
// The rest of the code here...
}
select is a Linq keyword, so you might want to rename that method to avoid any conflicts down the road. While I don't think it'd be causing any issues, I would change it.
You don't have to follow this convention, but in C# its customary to use an uppercase for the first letter of a method. Also note that the default access modifier for a class is internal... just want to make sure you're aware of that, as you continue your development.
Your last method, onPreviewMouseLeftButtonUp(...) has the following code in it:
SelectableTextBox textBox = (SelectableTextBox)sender;
Not only is it unsafe, as I've described above, but it also does absolutely nothing.
Lastly... and this is just me, I would probably move the code that handles changing the state of a selectable textbox (selected or not selected) into its class, since it belongs there. Why should anything else be in charge of how it handles its state. Keep things where they belong and you'll have a much easier time testing, debugging and maintaining your code. Don't fall into the "I'll refactor it later" trap... it'll rarely happen.
Here's my crude example. I let the class handle its MouseEnter event and simply check if the Mouse.LeftButton is down at that time. You would have to expand on it, but it should be a solid start:
Made some edits per OP's requests in the comments.
Preview:
XAML:
<Window x:Class="SelectableTextBoxes.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SelectableTextBoxes"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<local:SelectableTextBox Height="20" Width="100" Margin="10"/>
<local:SelectableTextBox Height="20" Width="100" Margin="10"/>
<local:SelectableTextBox Height="20" Width="100" Margin="10"/>
<local:SelectableTextBox Height="20" Width="100" Margin="10"/>
<local:SelectableTextBox Height="20" Width="100" Margin="10"/>
</StackPanel>
</Window>
C# (Pardon me for putting the SelectableTextBox into the same file...):
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace SelectableTextBoxes
{
// Move this class into its own file (it's here for prototyping).
public class SelectableTextBox : TextBox
{
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
if (_isSelected != value)
{
_isSelected = value;
// If the value changes, sets new color.
SetSelectionColor();
}
}
}
public SelectableTextBox()
{
// For handling an initial click if it happens in the textbox.
PreviewMouseDown += SelectableTextBox_PreviewMouseDown;
// For handling selection when mouse enters the textbox
// and left mouse button is down.
MouseEnter += SelectableTextBox_MouseEnter;
// To handle mouse capture (release it).
GotMouseCapture += SelectableTextBox_GotMouseCapture;
}
// Handles the mouse down event within the textbox.
void SelectableTextBox_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
if (!IsSelected)
{
IsSelected = true;
}
// If one of the Shift keys is down, return, since
// we don't want to deselect others.
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
{
return;
}
// This part makes a poor assumption that the parent is
// always going to be a Panel... expand on this code to
// cover other types that may contain more than one element.
var parent = VisualTreeHelper.GetParent(this) as Panel;
if (parent != null)
{
foreach (var child in parent.Children)
{
// If a child is not of a correct type, it'll be null.
var tbx = child as SelectableTextBox;
// This is where we check to see if it's null or this instance.
if (tbx != null && tbx != this)
{
tbx.IsSelected = false;
}
}
}
}
// When textbox receives focus, this event fires... we need to release
// the mouse to continue selection.
void SelectableTextBox_GotMouseCapture(object sender, MouseEventArgs e)
{
ReleaseMouseCapture();
}
// Sets selection state to true if the left mouse button is
// down while entering.
void SelectableTextBox_MouseEnter(object sender, MouseEventArgs e)
{
if (Mouse.LeftButton == MouseButtonState.Pressed)
{
IsSelected = true;
}
}
// Sets the background color based on selection state.
private void SetSelectionColor()
{
if (IsSelected)
{
Background = Brushes.LightCyan;
}
else
{
Background = Brushes.White;
}
}
}
// Window code... should be on its own, but I placed the two
// classes together while prototyping.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}

Categories