I am working on an application, wherein we have a main window which is having so many child windows in different dock option. So, one dock is having a property panel window which allows a user to modify property of selected entity and after changing the value user has to click on apply button which is available in the bottom of the control. So, I was willing to have some sort of functionality that if user has modified some value and instead of clicking on apply, if user click somewhere else apart from property panel view's sub control's, then user should be given a message that "Please first click apply to save your changes". for this what I did, I wrote the following piece of code on the mouse down event of MainWindow.
private void MainWindow_MouseDown(object sender, MouseButtonEventArgs e)
{
var hitObject = this.InputHitTest(e.GetPosition(this)) as DependencyObject;
if (hitObject.FindVisualAncestor<PropertyPanelUserControl>() == null)
{
MessageBox.Show("Please save your changes");
}
}
So, the logic is this, on mouse down of main window, get the hit object and check that if it is a child control of property panel control, then it will have PropertyPanelUserControl as its parent and other control which are not part or child control of PropertyPanelUserControl, then user will be prompted to click on the apply.
The above piece of code was working superb...but I figured out a strange issue, I had a combo box in the property panel which had entries from 1 to 10. So, when user tries to change value to other value, then user will not be given that message as, so far user is clicking on the property panel control and when i check the hit object in the mouse down event after selecting an item in the combo box, then hit object was the chromeButton or combo box. but When I selected the last item 10, then hit object comes as border which has the property panel control.
<Border><View:PropertyPanelControl/></Border> and above check fails, as border doesn't have ancestor as property panel control, rather border is the ancestor of the control. So, user gets a message even while changing only combo box value,
Moreover, I have made sure that I was clicking on the combo box item not outside, So, now question is this why wpf is behaving in this weird way and how to address this issue.
Your first question is strange:
why wpf is behaving in this weird way
You described what happens and it all seems totally normal to me. The user clicks on a ComboBoxItem and your HitTest tells you that you've clicked on a ComboBoxItem... I don't see any problem there.
how to address this issue
Now I imagine that if you had taken that ComboBoxItem and worked your way up the visual tree, then you would have found your PropertyPanelUserControl control. Try something like this instead:
HitTestResult result = VisualTreeHelper.HitTest(this, e.GetPosition(this));
UIElement uIElement = result.VisualHit.GetParentOfType<PropertyPanelUserControl>();
if (uIElement != null)
{
// the user clicked inside the PropertyPanelUserControl control
}
The GetParentOfType method is an Extension method that I created that walks up the visual tree looking for the first element of a particular type... you can easily refactor it into a normal method if you prefer:
public static T GetParentOfType<T>(this DependencyObject element) where T : DependencyObject
{
Type type = typeof(T);
if (element == null) return null;
DependencyObject parent = VisualTreeHelper.GetParent(element);
if (parent == null && ((FrameworkElement)element).Parent is DependencyObject) parent = ((FrameworkElement)element).Parent;
if (parent == null) return null;
else if (parent.GetType() == type || parent.GetType().IsSubclassOf(type)) return parent as T;
return GetParentOfType<T>(parent);
}
Related
I have a grid/panel on my UI that can take a drop action. The control is a Grid with some dynamically created content in it (a viewbox containing a stack panel of borders if that matters).
When I drag relevant information into the grid I want it to change (regenerate) the internal content and when it drops or leaves it should go back to normal. I have the 'change it' and 'put it back' methods working just fine, but I'm having trouble determining when to call them.
I have drag enter and drag leave events on the grid. The enter works great, but the leave is not. As soon as the mouse gets over the top of the content inside of the grid it fires a drag leave event, so it changes the content (and if that puts it back out of the child content it fires the drag enter again and just flashes).
My first thought was to just determine if the mouse is actually still over the grid and just ditch out without putting the content back. However this doesn't seem to be working. Here is my code for drag leave:
private void Grid_DragLeave(object sender, DragEventArgs e)
{
var wizard = DataContext as WizardVM;
var gd = sender as Grid;
if (wizard == null || gd == null || gd.IsMouseOver ||
gd.Children.Cast<FrameworkElement>().Any(x => x.IsMouseOver)) return;
wizard.Assembly.CollapsePreview();
}
As you can see, I even tried to iterate the children of the grid and see if mouse over is true on any of those and ditch out, and still it just keeps returning false for all of that and collapsing. I thought IsMouseOver was supposed to tell me if the mouse is over at all even if it's children of the control...
Ok well after some more research I found something that seems to be working. I ended up having to use VisualTreeHelper.HitTest and use the overload that has a callback and gets all hit items even if it's obscured. Here is my final code:
void Grid_DragLeave(object sender, DragEventArgs e)
{
var wizard = DataContext as WizardVM;
var gd = sender as Grid;
if (wizard == null || gd == null) return;
Point pt = e.GetPosition(this);
hitResults.Clear();
VisualTreeHelper.HitTest(gd, null, GridHitTestResultCallback,
new PointHitTestParameters(pt));
if (!hitResults.Contains(gd))
{
wizard.Assembly.IsExpanded = false;
}
}
HitTestResultBehavior GridHitTestResultCallback(HitTestResult result)
{
hitResults.Add(result.VisualHit);
return HitTestResultBehavior.Continue;
}
Basically it gets the point in relation to the full window, then calls HitTest with that point and the overall grid I'm trying to work with. The callback populates a list of visuals that are hit by the hit test. Last the original method checks if the grid is in the list of hits and if not it collapses it.
As a side note I also changed from .ExpandPreview() and .CollapsePreview() methods to a .IsExpanded property for unrelated reasons... didn't want anyone to be confused by that change...
I've created a class that inherits from Combobox, the class is editable. When a user have searched for a given predicate, I want them to be able to iterate the list with pressing the down key. This is somewhat implemented as it works sporadically. Is it any way to force focus to the popup element? I would like to do this by code, and not in XAML.
I've tried
var popup = Template.FindName("PART_Popup", this) as Popup;
popup.Focus();
Which return false as expected as this didn't work.
I've also tried
var s1 = Keyboard.FocusedElement;
if (s1 is FrameworkElement)
{
((FrameworkElement)s1).MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
But this focus on the next element in the TAB stack.
This focus approach didn't work either,
Keyboard.Focus(popup);
Edit:
I solved (achieved the desired effect) by setting the animation to .None, and then close followed by open the popup again.
I'm developing an on screen keyboard for use in a kiosk style application. I'm building it in such a way that I wish my on screen keyboard to display a preview of whatever text the user is entering using the keyboard.
In xaml I wish to add an attached property to input field controls within my application, for example a TextBox or ComboBox. I want the preview control on my OnScreenKeyboard to be bound to the same value that the underlying control it is attached to is. So if a user clicks on a TextBox, the preview on the on screen keyboard is also a TextBox and is also bound to the same underlying value as the TextBox, e.g. TextBox.Text.
The image I've provided above is how my keyboard will look. Because the keyboard itself is a popup at a fixed position, (Bottom center of the screen) the keyboard may cover the input control (TextBox, PasswordBox, ComboBox, RichTextBox etc...) that a user has clicked on to summon the keyboard, hence the requirement for the preview as part of the keyboard.
I know that in xaml I can create an attached property such as
<TextBox Text="{Binding Path=Entity.TextValue}" OSK.PopupKeyboard.UIElementControl="{How do I bind this to this parent control?}"/>
What I'd like to do is pass the parent control such as the textbox to the keyboard, set the preview bar along the top of my keyboard to be the same type with the same bindings as the underlying control that a user clicked on to summon the keyboard. This way the values that are entered into the preview on the keyboard are reflected on the control that the user clicked to summon the keyboard in the first place. I also figure it'll allow the keyboard to be flexible with the type of controls that can be used to summon it.
So I've figured out what I needed to do in this instance. I needed to create a dependency property of type FrameworkElement. On my on screen keyboard UserControl I needed a ContentControl to hold the FrameworkElement type. Naturally you have to disconnect the FrameworkElement from its original parent as it can't be in the visual tree more than once, store the parent, attach it to the new parent (ContentControl) then when you're done, reattach it to its original parent.
public static readonly DependencyProperty FrameworkElementProperty =
DependencyProperty.RegisterAttached("FrameworkElement",
typeof(FrameworkElement),
typeof(PopupKeyboard),
new FrameworkPropertyMetadata(default(FrameworkElement),
new PropertyChangedCallback(PopupKeyboard.OnFrameworkElementChanged)));
[AttachedPropertyBrowsableForType(typeof(FrameworkElement))]
public static FrameworkElement GetFrameworkElement(DependencyObject element)
{
if (element == null)
throw new ArgumentNullException("element");
return (FrameworkElement)element.GetValue(FrameworkElementProperty);
}
public static void SetFrameworkElement(DependencyObject element, bool value)
{
if (element == null)
throw new ArgumentNullException("element");
element.SetValue(FrameworkElementProperty, value);
}
private static void OnFrameworkElementChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FrameworkElement fe = d as FrameworkElement;
if (fe != null)
{
// detach here
keyboard.FrameworkElement = fe;
}
}
If I wanted to bind a control such as TextBox, ComboBox etc, I'd use the markup as follows:
<TextBox Content="{Binding Entity.Value}" local:PopupKeyboard.FrameworkElement="{Binding RelativeSource={RelativeSource Self}}" />
I have a wpf listbox with a custom item template which contains a rectangle.
The each item in the listbox can be selected (only one at a time).
I want to add a behavior in which when a user clicks on a place which isn't the item (for instance, a blank spot on the listbox, which is not an item), the selected item will become deselected.
Any ideas?
Thanks.
For example with a simple listbox:
item 1
item 2
The behavior that I'm looking for is when the user clicks on pixel 500 (which is a part of the listbox but not on an item), the currently selected item will be deselected.
The simple solution is to data bind a property to the ListBox.SelectedItem property and set it to null whenever you want to clear the selection:
<ListBox ItemsSource="{Binding YourItems}" SelectedItem="{Binding SelectedItem}"
SelectionMode="Single" />
Then in code, you can just do this to clear the selection:
SelectedItem = null;
And when would you do that? You can attach a handler to the PreviewMouseLeftButtonDown event of the Window, or any other control in your UI. In the handler method, you could do a hit test to see what the item the user clicked on was:
HitTestResult hitTestResult =
VisualTreeHelper.HitTest(controlClickedOn, e.GetPosition(controlClickedOn));
Control controlUnderMouse = hitTestResult.VisualHit.GetParentOfType<Control>();
See the VisualTreeHelper.HitTest Method (Visual, Point) for more help with this part.
Then maybe something like this:
if (controlUnderMouse.GetType() != typeof(ListBoxItem)) SelectedItem = null;
Of course, there are many ways to do this, and you'll have to fill in the few blank spots that I left, but you should get the idea.
EDIT >>>
The generic GetParentOfType method is a custom Extension Method that is defined in a separate class named DependencyObjectExtensions:
public static class DependencyObjectExtensions
{
public static T GetParentOfType<T>(this DependencyObject element)
where T : DependencyObject
{
Type type = typeof(T);
if (element == null) return null;
DependencyObject parent = VisualTreeHelper.GetParent(element);
if (parent == null && ((FrameworkElement)element).Parent is DependencyObject)
parent = ((FrameworkElement)element).Parent;
if (parent == null) return null;
else if (parent.GetType() == type || parent.GetType().IsSubclassOf(type))
return parent as T;
return GetParentOfType<T>(parent);
}
...
}
For The each item in the listbox can be selected (only one at a time).
You can come up with one of followings
1- Disable the item after it is selected.
2- Maintain a list at backend to mark each index selectable or unselectable.
To assure that only one item is selected put this in the listbox:
SelectionMode="Single"
then for the unselect when clicking somewhere, you can try to check this events
PreviewMouseLeftButtonUp
LostFocus()
Regards,
I was searching for a solution to this problem, but to prevent the last item in the listbox from becoming selected when clicking on the blank space. my problem is slightly different but has the same solution that I have come up with which works for me.
Although I am using powershell and not c#, I am still utilizing the windows forms listbox control so I think the idea will be applicable.
Also, I couldn't find any discussions of this problem specifically dealing with powershell when I was searching for a solution, so I wound up here.
so I created a variable, maxY, and multiplied the number of list items by the itemheight.
next, at the beginning of the mouse_up event, I just check if the Y location from the mouse click is less than the maxY variable. if true, select the item and run your code, if not, do nothing.
I can only provide a code sample in powershell, but I think the idea is portrayed.
$listbox.add_MouseUP({
$maxY = $this.items.count * $this.itemHeight
if ($_.y -le $maxY) {
$this.SelectedIndex = $this.IndexFromPoint($_.X, $_.y)
#do stuff here
}
else {
$this.clearselection()
}
}
This will clear all selections if clicking on blank space, but will also prevent an item from being selected when clicking on blank space.
How do I implement a Copy menu item in a Windows application written in C#/.NET 2.0?
I want to let the user to mark some text in a control and then select the Copy menu item from an Edit menu in the menubar of the application and then do a Paste in for example Excel.
What makes my head spin is how to first determine which child form is active and then how to find the control that contains the marked text that should be copied to the clipboard.
Help, please.
With the aid of some heavy pair programming a colleague of mine and I came up with this, feel free to refactor.
The code is placed in the main form. The copyToolStripMenuItem_Click method handles the Click event on the Copy menu item in the Edit menu.
/// <summary>
/// Recursively traverse a tree of controls to find the control that has focus, if any
/// </summary>
/// <param name="c">The control to search, might be a control container</param>
/// <returns>The control that either has focus or contains the control that has focus</returns>
private Control FindFocus(Control c)
{
foreach (Control k in c.Controls)
{
if (k.Focused)
{
return k;
}
else if (k.ContainsFocus)
{
return FindFocus(k);
}
}
return null;
}
private void copyToolStripMenuItem_Click(object sender, EventArgs e)
{
Form f = this.ActiveMdiChild;
// Find the control that has focus
Control focusedControl = FindFocus(f.ActiveControl);
// See if focusedControl is of a type that can select text/data
if (focusedControl is TextBox)
{
TextBox tb = focusedControl as TextBox;
Clipboard.SetDataObject(tb.SelectedText);
}
else if (focusedControl is DataGridView)
{
DataGridView dgv = focusedControl as DataGridView;
Clipboard.SetDataObject(dgv.GetClipboardContent());
}
else if (...more?...)
{
}
}
Why not extending the control, so the control itself provides the data which should be copied into the clipboard.
Take a look at ApplicationCommands documentation.
To determine which window is open, you can query the Form.ActiveMDIChild property to get a reference to the currently active window. From there, you can do one of two things:
1) If you create your own custom Form class (FormFoo for example) that has a new public member function GetCopiedData(), then inherit all of your application's child forms from that class, you can just do something like this:
((FormFoo)this.ActiveMDIChild).GetCopiedData();
Assuming the GetCopiedData function will have the form-specific implementation to detect what text should be copied to the clipboard.
or
2) You can use inheritance to detect the type of form that is active, and then do something to get the copied data depending on the type of form:
Form f = this.ActiveMDIChild;
if(f is FormGrid)
{
((FormGrid)f).GetGridCopiedData();
} else if(f is FormText) {
((FormText)f).GetTextCopiedData();
}
etc.
That should get you started with finding the active window and how to implement a copy function. If you need more help copying out of a GridView, it may be best to post another question.
If the form is tabbed and the target control is a DataGridView, it's sometimes possible for the Form's TabControl to be returned as the active control, using the above method, when the DataGridView is right clicked upon.
I got around this by implementing the following handler for my DataGridView:-
private void dataGridView_CellMouseDown(object sender, DataGridViewCellMouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
dataGridView.Focus();
dataGridView.CurrentCell = dataGridView[e.ColumnIndex, e.RowIndex];
}
}
It seems to me that you might be better off breaking this into smaller tasks/questions.
You have a few issues you are stuck on from the way it sounds.
You have multiple 'child' windows open. Is this an MDI application?
When an action is performed on one of those child windows, it should fire an event in that window's event handlers. That is your first thing to set up. If this is a datagridview I would suggest a simple test to start. Try trapping the DataGridView.SelectionChanged event. Just throw in something like MessageBox.Show("I copied your datas!"); for now.
This should get you started where you will at least understand how this event will be raised to you.
From here, we will need to know a little more about your datagrid, and the rows and child controls in those rows. Then we can likely create events in the render events that will be raised at the appropriate times, with the appropriate scope.