I'm trying to correctly remove a UIElement from an InlineUIContainer in order to use it in another Panel but the program keeps crashing with this message "Specified Visual is already a child of another Visual or the root of a CompositionTarget.".
I've created a small application to illustrate my pain. In this program, once Randy the button is killed\deleted by his girlfriend, he doesn't still detach from his parent, whom I got find out was UIElementIsland. And then any attempt to add Randy as the child of anything else crashes the application (The Apocalypse Button proves my point :) ). You can click to check Randy's parents before\after deleting Randy to notice that he is constantly under UIElementIsland as a child, If he is detached the whole problem\apocalypse should be averted.
It's a Funny application so copy and compile even if it's just for the fun! Any help\ideas would be appreciated!
THE C# Part:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
namespace DetachingfromUIElementIsland
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
int t = 0;
static string[] info = new string[] { "Okay, Lets have a look...", "Checking."
, "Checking..", "Checking...", "Seen it!" };
/// <summary>
/// Makes the App fancy :)
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void timer_Tick(object sender, EventArgs e)
{
display.Text = info[t];
if (t == 0)
timer.Interval = new TimeSpan(0, 0, 0, 0, 300);
t++;
if (t >= 4)
{
t = 0;
timer.Stop();
display.Text = GetRandysParent();
}
}
private void deleteRandy_Click(object sender, RoutedEventArgs e)
{
// This might be the bug.
// Maybe there's a better way to do this.
// If there was a VisualTreeHelper.Remove().
randy_container.Child = null;
display.Text = "Haha! I just killed Randy!!! He'll never get the chance"
+ "\n to hurt another woman again!";
display.Background = Brushes.Violet;
end.Visibility = System.Windows.Visibility.Visible;
}
DispatcherTimer timer = null;
/// <summary>
/// Check if Randy is Still attached to UIElementIsland
/// </summary>
/// <returns></returns>
private string GetRandysParent()
{
// Check the visual tree to see if randy is removed properly
DependencyObject dp = VisualTreeHelper.GetParent(randy);
string text = string.Empty;
if (dp != null)
{
display.Background = Brushes.LightGreen;
text = "Randy's Dad is Mr " + dp.ToString();
}
else
{
// This should be what you'll get when the code works properly
display.Background = Brushes.Red;
text = "Weird...Randy doesn't seem to have a dad...";
}
return text;
}
private void findParents_Click(object sender, RoutedEventArgs e)
{
display.Background = Brushes.Yellow;
// Creates a timer to display some fancy stuff
// and then Randy's.
// Just to prove to you that this button actually works.
timer = new DispatcherTimer();
timer.Start();
timer.Tick += timer_Tick;
timer.Interval = new TimeSpan(0, 0, 0, 0, 700);
}
private void randy_Click(object sender, RoutedEventArgs e)
{
// Get Randy to introduce himself
display.Text = "Hi, I'm Randy!!!";
display.Background = Brushes.Orange;
}
private void end_Click(object sender, RoutedEventArgs e)
{
// If randy is removed properly, this would not crash the application.
StackPanel s = new StackPanel();
s.Children.Add(randy);
// CRASH!!!
}
}
}
The XAML:
<Window x:Class="DetachingfromUIElementIsland.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">
<FlowDocument IsEnabled="True" x:Name="document">
<Paragraph>
<InlineUIContainer x:Name="randy_container">
<!--Meet Randy-->
<Button Name="randy" Content="I am a Randy, the button" Click="randy_Click" ToolTip="Meet Randy"/>
</InlineUIContainer>
<LineBreak/>
<LineBreak/>
<InlineUIContainer x:Name="container2">
<!--Meet Randy's Ex Girlfriend-->
<Button Name="deleteRandy" Content="Randy dumped me for another girl :(, click me to delete him" Click="deleteRandy_Click" ToolTip="Meet Randy's Ex Girlfriend"/>
</InlineUIContainer>
<LineBreak/>
<LineBreak/>
<InlineUIContainer x:Name="container3">
<!--He can help you find Randy's Parents-->
<Button Name="findParents" Content="Click me to find randy's parents" Click="findParents_Click" ToolTip="He can help you find Randy's Parents"/>
</InlineUIContainer>
<LineBreak/>
<LineBreak/>
<InlineUIContainer x:Name="Apocalypse">
<!--End the world, Crash the application-->
<Button x:Name="end" Content="Avenge Randy's Death" Click="end_Click" ToolTip="End the world, Crash the application" Visibility="Hidden"/>
</InlineUIContainer>
</Paragraph>
<Paragraph>
<InlineUIContainer>
<TextBlock x:Name="display" Foreground="Black"/>
</InlineUIContainer>
</Paragraph>
</FlowDocument>
</Window>
The whole code was supposed to be shorter than this, but I spiced it up to make it a bit fun. Hope I brightened someone's day a little. But still, help me :).
Answer:
Derive from Randy's InlineUIContainer as follows:
public class DerivedInlineUIContainer : InlineUIContainer
{
public DerivedInlineUIContainer()
{
}
public void RemoveFromLogicalTree(FrameworkElement f)
{
this.RemoveLogicalChild(f);
}
}
Now you could kill Randy properly this time, and add him to UIElement heaven (The StackPanel):
randy_container.RemoveFromLogicalTree(randy);
IDisposable disp = VisualTreeHelper.GetParent(randy) as IDisposable;
if (disp != null)
disp.Dispose();
// Poor Randy is going to heaven...
StackPanel heaven = new StackPanel();
heaven.add(randy);
Thanks everyone.
Removing the visual parent doesn't seem to help:
private void end_Click(object sender, RoutedEventArgs e)
{
IDisposable disp = VisualTreeHelper.GetParent(randy) as IDisposable;
if (disp != null)
disp.Dispose();
DependencyObject parent = VisualTreeHelper.GetParent(randy);
if (parent == null)
MessageBox.Show("No parent");
// If randy is removed properly, this would not crash the application.
StackPanel s = new StackPanel();
s.Children.Add(randy);
}
So you could either create a new Button:
public MainWindow()
{
InitializeComponent();
randy_container.Child = CreateRandyButton();
}
private void end_Click(object sender, RoutedEventArgs e)
{
StackPanel s = new StackPanel();
s.Children.Add(CreateRandyButton());
}
private Button CreateRandyButton()
{
Button button = new Button { Name = "randy", Content = "I am a Randy, the button", ToolTip = "Meet Randy" };
button.Click += randy_Click;
return button;
}
...or simply hide it as suggested by #Sinatr.
It's funny, but also very noisy. You would get answer much faster if your demo is short.
Instead of removing/adding visual you can simply hide/show it:
void deleteRandy_Click(object sender, RoutedEventArgs e) =>
randy.Visibility = Visibility.Hidden;
void end_Click(object sender, RoutedEventArgs e) =>
randy.Visibility = Visibility.Visible;
This way you are not playing with visual tree in unrecoverable way. You can use MVVM + data templates or x:Shared=False resources if you really want to remove UI element and then add new one.
I found a workaround in case the parent is still a UIElementIsland. Since it implements IDisposable, you can clear its children that way:
var parent = VisualTreeHelper.GetParent(element);
if (parent is IDisposable uiElementIsland)
{
uiElementIsland.Dispose();
}
It's not nice, but it works.
Related
I need help from someone who work in C#. I'm trying to achieve WPF application that open internet explorer on URL that i supplement him as a parameter. So in perfect world i would like run something like this:
\\path\window.exe X x Y "URL" "Title"
Where X,Y is windows height and width, URL and Title is a title of that window. My knowledge of C# is nonexistent so after extensive use of google i manage to get this XAML:
<Window x:Class="WPFWebControl.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="My Web Browser" WindowState="Normal" Loaded="Window_Loaded" WindowStyle="ThreeDBorderWindow" mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" Height="Auto" Width="Auto">
<Grid>
<WebBrowser Height="Auto" HorizontalAlignment="Left" Margin="0,28,0,0" Name="MyWebBrowser" VerticalAlignment="Top" Width="Auto" LoadCompleted="MyWebBrowser_LoadCompleted" />
<TextBox Height="23" Margin="40,5,0,0" Name="MyTextBox" VerticalAlignment="Top" HorizontalAlignment="Left" Width="708" />
<Button Content="Go" Margin="10,5,0,476" Name="MyGo" ToolTip="Go" Click="MyGo_Click" RenderTransformOrigin="-0.76,0.565" HorizontalAlignment="Left" Width="25" />
</Grid>
</Window>
and this C# code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WPFWebControl
{ /// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
try
{
MyWebBrowser.Source = new Uri("http://www.google.com");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void MyGo_Click(object sender, RoutedEventArgs e)
{
try
{
MyWebBrowser.Source = new Uri("http://" + MyTextBox.Text);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void MyWebBrowser_LoadCompleted(object sender, NavigationEventArgs e)
{}
}
}
Now i need some advice how can i set window size, url and title via parameters so i can get rid of that address textbox and button.
You could open a new window and set a WebBrowser as its Content, e.g.:
WebBrowser browser = new WebBrowser();
Window win = new Window();
win.Content = browser;
win.Height = 100;
win.Width = 100;
win.Show();
win.Title = "title...";
browser.Navigate("http://google.com");
If you want your application to be able to accept command line arguments, you could remove the StartupUri attribute from your App.xaml and override the OnStartup method of your App.xaml.cs:
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
try
{
double width = Convert.ToDouble(e.Args[0]);
double height = Convert.ToDouble(e.Args[1]);
string title = e.Args[2];
string url = e.Args[3];
WebBrowser browser = new WebBrowser();
Window win = new Window();
win.Content = browser;
win.Height = height;
win.Width = width;
win.Show();
win.Title = title;
browser.Navigate(new Uri(url, UriKind.Absolute));
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
Usage:
WpfApplication1.exe 100 100 "title..." "http://google.com"
You should be able to use
string[] args = Environment.GetCommandLineArgs();
Or, change teh startup uri in App.xaml (remove it) and instead create your own startup like
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
//Manually create and show your window, passing the arguments
}
}
etc
can not make it work, whatever I tried so far.
I want to make a Remote Camera Commander application (WPF/C#) in Visual Studio 2015 that consists of a number buttons that, when clicked, do a web request to a network camera. I am not a programmer, so I am starting from scratch.
Have searched the internet for many days now and tested many examples, but when inserting a piece of example code into my code, always new issues arise.
I have made an example that hopefully explains my issues:
MainWindow.xaml:
<Window x:Class="RCC_1v1.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:RCC_1v1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button x:Name="button" Content="GetCameraType_Button" Click="GetType" HorizontalAlignment="Left" Margin="44,108,0,0" VerticalAlignment="Top" Width="168" Height="86"/>
<RichTextBox x:Name="response" HorizontalAlignment="Left" Height="78" VerticalAlignment="Top" Width="169" Margin="246,112,0,0" TextChanged="response_TextChanged">
<FlowDocument>
<Paragraph>
<Run Text ="How to get the response from webclient into this textbox, instead of in the messagebox??"/>
</Paragraph>
</FlowDocument>
</RichTextBox>
</Grid>
MainWindow.xaml.cs:
using System;
using System.IO;
using System.Net;
using System.Windows;
using System.Windows.Controls;
namespace RCC_1v1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private void GetType(object sender, RoutedEventArgs e)
{
WebClient wc = new WebClient();
wc.Credentials = new NetworkCredential("stackO", "12345");
wc.DownloadStringAsync(new Uri("http://eremote-cam1.eu.ngrok.io/axis-cgi/param.cgi?action=list&group=root.Brand"));
wc.DownloadStringCompleted += new DownloadStringCompletedEventHandler(wc_DownloadStringCompleted);
}
private void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
MessageBox.Show(e.Result.ToString());
}
private void response_TextChanged(object sender, TextChangedEventArgs e)
{
}
}
}
You can run it yourself with the temp. the account that is embedded. Not sure if I should use TextBox or any other object like listview or.
The expected responses are text-string and single numbers.
Any tip, direction or help is appreciated.
Just followed some of your suggestions, code is now:
enter code hereusing System;
using System.Net;
using System.Windows;
namespace RCC_1v2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private void GetType(object sender, RoutedEventArgs e)
{
WebClient wc = new WebClient();
wc.Credentials = new NetworkCredential("stackO", "54321");
wc.DownloadStringAsync(new Uri("http://eremote-cam1.eu.ngrok.io/axis-cgi/param.cgi?action=list&group=root.Brand.ProdFullName"));
wc.DownloadStringCompleted += new DownloadStringCompletedEventHandler(wc_DownloadStringCompleted);
}
private void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
this.Dispatcher.BeginInvoke(new Action(() => {
String run_text = e.Result.ToString();
Run1.Text = run_text.Substring(24, 13);
}));
}
}
}
enter image description here
Give the name "Run1" to the Run contained in your XAML using x:Name attribute.
Replace your code:
MessageBox.Show(e.Result.ToString());
with this one:
this.Dispatcher.BeginInvoke(new Action(() => {
this.Run1.Text = e.Result.ToString();
}));
The event DownloadStringCompleted runs in a background thread, so if you want to update the UI you must use the Dispatcher property of the Window class.
Ok I just found out about the idea of posting an answer. With help of the posters I created the next example/solution:
using System;
using System.Net;
using System.Windows;
namespace RCC_1v2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private void GetType(object sender, RoutedEventArgs e)
{
WebClient wc = new WebClient();
wc.Credentials = new NetworkCredential("xxx", "xxx");
wc.DownloadStringAsync(new Uri("http://eremote-cam1.eu.ngrok.io/axis-cgi/param.cgi?action=list&group=root.Brand.ProdFullName"));
wc.DownloadStringCompleted += new DownloadStringCompletedEventHandler(wc_DownloadStringCompleted);
}
private void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
this.Dispatcher.BeginInvoke(new Action(() => {
String run_text = e.Result.ToString();
Run1.Text = run_text.Substring(24, 13);
}));
}
}
}
Mainwindow.xaml:
<Window x:Class="RCC_1v2.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:RCC_1v2"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button x:Name="button" Content="GetCameraType_Button" Click="GetType" HorizontalAlignment="Left" Margin="44,108,0,0" VerticalAlignment="Top" Width="140" Height="86"/>
<TextBox x:Name="Run1" HorizontalAlignment="Left" Height="78" VerticalAlignment="Top" Width="285" Margin="206,113,0,0" >
</TextBox>
</Grid>
</Window>
As you can see I now have the response in the texbox (and not a RichTextBox)
nb. I was not allowed to post a picture :-(
enter image description here
Thanks!
Currently users can draw on the canvas by clicking and dragging their mouse. How can I change the color of the line to indicate the user's cursor is hovering over the line? It would be ideal to make it highlight when the cursor is within 5 pixels of any given line to indicate they are close.
Inital drawing...
When user's cursor is either hovering directly over or within 5 pixels of any given line.
MainWindow.xaml
<Window x:Class="WpfApplication1.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:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Canvas Name="paintSurface" MouseDown="Canvas_MouseDown_1" MouseMove="Canvas_MouseMove_1" >
<Canvas.Background>
<SolidColorBrush Color="White" Opacity="0"/>
</Canvas.Background>
</Canvas>
</Grid>
</Window>
MainWindow.cs
using System.Windows;
using System.Windows.Input;
using System.Windows.Shapes;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
Point currentPoint = new Point();
public MainWindow()
{
InitializeComponent();
}
private void Canvas_MouseDown_1(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (e.ButtonState == MouseButtonState.Pressed)
currentPoint = e.GetPosition(this);
}
private void Canvas_MouseMove_1(object sender, System.Windows.Input.MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
Line line = new Line();
line.Stroke = SystemColors.WindowFrameBrush;
line.X1 = currentPoint.X;
line.Y1 = currentPoint.Y;
line.X2 = e.GetPosition(this).X;
line.Y2 = e.GetPosition(this).Y;
currentPoint = e.GetPosition(this);
paintSurface.Children.Add(line);
}
}
}
}
Note: There are two versions of the sample program here, new and old. Take a look at both to get an idea of what you can do.
Here's a sample application that fixes the issue you were unaware of, which is the fact that you drew numerous lines with one stroke and not a single line. You should use a Polyline. If you use Visual Studio 2015, then there's a Live Visual Tree, which will show you exactly what I mean; otherwise, you can use a tool such as Snoop to see the same thing. It also addresses you original question, which is the highlighting.
The new version is the first code portion that is shown here. It uses a dictionary to link base line and highlight lines, so that you may get to the underlying base line if need to (such as when you want to delete it). It also highlights the base, versus the highlighting the highlight line, which is what the old version did. The highlight line is simply used for the selection zone buffer. Increase or decrease its stroke to get the desired selection buffer (you mentioned 5 pixels in your post).
Preview:
XAML:
<Window x:Class="WpfApplication.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:WpfApplication"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Canvas Name="paintSurface" Background="White" MouseDown="Canvas_MouseDown"
MouseUp="Canvas_MouseUp" MouseMove="Canvas_MouseMove"/>
</Window>
C#:
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
namespace WpfApplication
{
public partial class MainWindow : Window
{
Polyline _baseLine;
Polyline _highlightLine;
Point _currentPoint;
bool _newLine;
Dictionary<Polyline, Polyline> _lines = new Dictionary<Polyline, Polyline>();
public MainWindow()
{
InitializeComponent();
}
private void Canvas_MouseDown(object sender, MouseButtonEventArgs e)
{
_newLine = true;
}
private void Canvas_MouseUp(object sender, MouseButtonEventArgs e)
{
if (_highlightLine != null && !_newline)
{
_highlightLine.MouseEnter += ShowHighlight;
_highlightLine.MouseLeave += HideHighlight;
}
}
private void Canvas_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
if (_newLine)
{
_baseLine = new Polyline
{
Stroke = SystemColors.WindowFrameBrush,
StrokeThickness = 1.0
};
_highlightLine = new Polyline
{
Opacity = 0.0,
Stroke = SystemColors.WindowFrameBrush,
StrokeThickness = 10.0
};
paintSurface.Children.Add(_baseLine);
paintSurface.Children.Add(_highlightLine);
_lines.Add(_highlightLine, _baseLine);
_newLine = false;
}
_currentPoint = e.GetPosition(this);
_baseLine.Points.Add(_currentPoint);
_highlightLine.Points.Add(_currentPoint);
}
}
private void ShowHighlight(object sender, MouseEventArgs e)
{
var line = sender as Polyline;
if (line != null)
{
_lines[line].Stroke = new SolidColorBrush(Colors.LimeGreen);
}
}
private void HideHighlight(object sender, MouseEventArgs e)
{
var line = sender as Polyline;
if (line != null)
{
_lines[line].Stroke = SystemColors.WindowFrameBrush;
}
}
}
}
You'll notice the _newLine flag boolean. I use it to indicate whether a new Polyline should be drawn. When the mouse is down, that's an indicator that a new line needs to be created. I don't hook up the MouseEnter and MouseLeave handles for the line until the mouse is up because I don't want highlighting to be distracting during the drawing process of the line. You have to give some sort of stroke to the _highlightLine and then set its opacity to 0 to make it invisible, but still respond to hit tests; otherwise, MouseEnter and MouseLeave handlers will never get invoked.
OLD (The old version of the program. Still a good one to check out.):
What I do here is add a highlighting polyline on top of the base one and set its stroke to be 10 instead of the base's 1. You can adjust that stroke thickness to get yourself the desired selection "buffer" zone. I literally spent about 10-15 minutes on this, so there could be ways to improve it, but this should give you a solid base to build upon. If you wish to perform some actions down the road on these lines you're highlighting, such being able to delete them, then I suggest adding both the _baseLine and the _highlightLine to a dictionary, where _highlightLine is the key and _baseLine is the value. That way, when you select the _highlightLine, you may access the underlying _baseLine.
Preview:
XAML:
<Window x:Class="WpfApplication.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:WpfApplication"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Canvas Name="paintSurface" Background="White" MouseDown="Canvas_MouseDown"
MouseUp="Canvas_MouseUp" MouseMove="Canvas_MouseMove"/>
</Window>
C#:
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
namespace WpfApplication
{
public partial class MainWindow : Window
{
Polyline _baseLine;
Polyline _highlightLine;
Point _currentPoint;
bool _newLine;
public MainWindow()
{
InitializeComponent();
}
private void Canvas_MouseDown(object sender, MouseButtonEventArgs e)
{
_newLine = true;
}
private void Canvas_MouseUp(object sender, MouseButtonEventArgs e)
{
if (_highlightLine != null && !_newline)
{
_highlightLine.MouseEnter += ShowHighlight;
_highlightLine.MouseLeave += HideHighlight;
}
}
private void Canvas_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
if (_newLine)
{
_baseLine = new Polyline
{
Stroke = SystemColors.WindowFrameBrush,
StrokeThickness = 1.0
};
_highlightLine = new Polyline
{
Stroke = new SolidColorBrush(Colors.Green),
Opacity = 0.0,
StrokeThickness = 10.0
};
paintSurface.Children.Add(_baseLine);
paintSurface.Children.Add(_highlightLine);
_newLine = false;
}
_currentPoint = e.GetPosition(this);
_baseLine.Points.Add(_currentPoint);
_highlightLine.Points.Add(_currentPoint);
}
}
private void ShowHighlight(object sender, MouseEventArgs e)
{
var line = sender as Polyline;
if (line != null)
{
line.Opacity = 1.0;
}
}
private void HideHighlight(object sender, MouseEventArgs e)
{
var line = sender as Polyline;
if (line != null)
{
line.Opacity = 0.0;
}
}
}
}
Additional Thoughts:
If you want to go full XAML as far as styling, you've got a few options. First option is to create a style that highlights TargetType Polyline on IsMouseOver property being true; however, you won't get the 5 pixel buffer with this one. To accomplish that 5 pixel buffer, you'd need to create a custom template, which requires more work than what I've demonstrated here. Of course... if you're feeling very adventurous, there's always the option of deriving from Shape and create yourself a highlightable/selectable Polyline -- it's just a lot of work, compared to the above code. The bright side is that it'll be reusable. It just depends on your situation, needs and wants.
the following code was supposed to turn all the buttons present in the form into green color on one of the button clicks due to event bubbling, but on my machine on Visual studio 2008, it is turning only the clicked button as green, could you please help in figuring out the problem?
XAML Code (window1.xaml):
<Window x:Class="EventRouting.Window1" Title="Event Routing" Height="300" Width="300"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Border Margin="15" BorderBrush="Blue" BorderThickness="5" Padding="15"
CornerRadius="12" x:Name="myBorder" Background="Transparent">
<StackPanel x:Name="myPanel" Background="Transparent">
<Ellipse x:Name="myEllipse" Margin="3" Fill="Green" Height="40" />
<Rectangle x:Name="myRectangle" Margin="3" Fill="Cyan" Height="40" RadiusX="10" RadiusY="10" />
</StackPanel>
CS code (window1.xaml.cs)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Diagnostics;
namespace EventRouting
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
this.MouseEnter += MouseEnterHandler;
myBorder.MouseEnter += MouseEnterHandler;
myPanel.MouseEnter += MouseEnterHandler;
myEllipse.MouseEnter += MouseEnterHandler;
myRectangle.MouseEnter += MouseEnterHandler;
this.MouseDown += MouseDownHandler;
myBorder.MouseDown += MouseDownHandler;
myPanel.MouseDown += MouseDownHandler;
myEllipse.MouseDown += MouseDownHandler;
myRectangle.MouseDown += MouseDownHandler;
for (int i = 1; i <= 5; ++i)
{
Button btn = new Button();
btn.Content = "Button " + i;
myPanel.Children.Add(btn);
//btn.Click += new RoutedEventHandler(btn_Click);
}
myPanel.AddHandler(Button.ClickEvent, new RoutedEventHandler(btn_Click));
}
void btn_Click(object sender, RoutedEventArgs e)
{
Button btn = (Button) e.Source;
btn.Background = Brushes.Green;
}
void MouseEnterHandler(object sender, MouseEventArgs e)
{
Debug.WriteLine("MouseEnter: " + sender);
}
void MouseDownHandler(object sender, MouseButtonEventArgs e)
{
Debug.WriteLine("MouseDown: " + sender);
e.Handled = true;
}
}
}
Routed events bubble up the visual tree until they get handled. Using your XAML, try this code out.
public MainWindow()
{
InitializeComponent();
myEllipse.AddHandler(UIElement.MouseDownEvent, new RoutedEventHandler(OnMouseDown));
myPanel.AddHandler(UIElement.MouseDownEvent, new RoutedEventHandler(OnMouseDown));
myBorder.AddHandler(UIElement.MouseDownEvent, new RoutedEventHandler(OnMouseDown));
}
void OnMouseDown(object sender, RoutedEventArgs e)
{
UIElement uiElement = sender as UIElement;
Debug.WriteLine(uiElement.GetType().ToString());
e.Handled = true;
}
if you comment out the e.Handled = true line, the event will bubble up to parent elements. Here is a good link for you.
If I understand what you want is a tunnel event from the root (the panel) to each child (the buttons). Routed tunnel events don't do that, they only traverse from the root to the the source element, not to siblings. See Overview of routed events in WPF with a great illustration of this.
Because the event handler btn_Click(object sender, MouseEventArgs e) is sending the button you clicked only you can't use the sender object alone to change the text to green.
You should be doing something like foreach (Button btn in myPanel.Children) where you loop through all of your buttons and change the color in your loop.
I have a very simple application where I'm trying to bind keyboard shortcuts to a WPF command that is bound to a menu item. The application itself consists of just a Menu and a WebBrowser control.
When I'm within the WebBrowser, the keyboard shortcuts are not routed up to the WPF Menu. For example, typing 'Ctrl+O' when focused in the web browser shows the IE open page. Additionally, in this application, unless I have the Menu focused (by typing in Alt) the input bindings don't fire. For example, I can't focus on the WPF window by clicking on the title bar and then type the shortcuts. The full code is replicated below:
MainWindow.xaml
<Window x:Class="TestInputBindingsOnMenu.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="600" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Menu IsMainMenu="True" x:Name="_mainMenu" Grid.Row="0" />
<WebBrowser Source="http://google.com" Grid.Row="1" />
</Grid>
</Window>
MainWindow.xaml.cs
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace TestInputBindingsOnMenu
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Initialize();
}
private void Initialize()
{
MenuItem fileMenu = new MenuItem();
MenuItem fileNew = new MenuItem();
MenuItem fileOpen = new MenuItem();
MenuItem fileExit = new MenuItem();
fileMenu.Header = "File";
fileNew.Header = "New";
fileOpen.Header = "Open";
fileExit.Header = "Exit";
fileMenu.Items.Add(fileNew);
fileMenu.Items.Add(fileOpen);
fileMenu.Items.Add(fileExit);
_mainMenu.Items.Add(fileMenu);
var fileNewCommand = CreateCommand("New");
var fileOpenCommand = CreateCommand("Open");
var fileExitCommand = CreateCommand("Exit");
_mainMenu.CommandBindings.Add(new CommandBinding(fileNewCommand, ExecuteNew));
_mainMenu.CommandBindings.Add(new CommandBinding(fileOpenCommand, ExecuteOpen));
_mainMenu.CommandBindings.Add(new CommandBinding(fileExitCommand, ExecuteExit));
fileNew.Command = fileNewCommand;
fileOpen.Command = fileOpenCommand;
fileExit.Command = fileExitCommand;
_mainMenu.InputBindings.Add(new InputBinding(fileNewCommand, new KeyGesture(Key.N, ModifierKeys.Control)));
_mainMenu.InputBindings.Add(new InputBinding(fileOpenCommand, new KeyGesture(Key.O, ModifierKeys.Control)));
_mainMenu.InputBindings.Add(new InputBinding(fileExitCommand, new KeyGesture(Key.F4, ModifierKeys.Alt)));
}
private void ExecuteNew(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show("New!!");
}
private void ExecuteOpen(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show("Open!!");
}
private void ExecuteExit(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show("Exit!!");
}
private static RoutedCommand CreateCommand(string label)
{
return new RoutedCommand(label, typeof(MainWindow));
}
}
}
Easy Solution
Add the input bindings to the WebBrowser control as well as the main menu:
Browser.InputBindings.Add(new KeyBinding(ApplicationCommands.Open,
new KeyGesture(Key.O, ModifierKeys.Control)));
Hard Solution
What is happening here is that the UIElement is using the KeyDown event, while you want to use PreviewKeyDown event, or add a handler that also handles Handled routed events. Look up Tunnelling and Bubbling if you don't know about this.
Since this is handled in the UIElement class, I would advise using a different pattern in this situation. The MVVM Light framework provides an EventToCommand behavior. If you can route the PreviewKeyDown event of the window to the right commands instead of using KeyBinding with the InputBindings collection of the UIElement you will have your solution.
You will need some custom code to check which key was pressed and to which command the routing should be.