Drag/drop controls onto child Canvas without parent Canvas being notified - c#

I'm trying to create a visual "designer" that will allow users to drag controls from a toolbox onto a design canvas. The excellent tutorial here has helped me get a basic system up and going - I can drag controls from the toolbox, select and resize them etc. Amongst other things,the code uses a modified Canvas control, overriding the OnDrop method.
However, I'd like to give the user the option of defining "panels" within the design: effectively smaller Canvas's containing the toolbox controls - as an example:
So when the user drags the button onto Canvas_1, OnDrop fires and all is good. However, if the user creates Canvas_2, and then drags the button onto Canvas_2 - itself a child control of Canvas_1 - the parent OnDrop still fires and the button is added to Canvas_1.
I've tried setting the ZIndex of Canvas_2 to greater than Canvas_1, to no avail - the parent Canvas always gets the event. How can I ensure that Canvas_2 gets the OnDrop events for controls landing on it? Is there a different approach I should be using?

The event DragDrop.DragEnter uses a bubbling routing strategy, that means the event will travel upwards in the visual tree starting from the source element, giving a chance for all the elements along the route to handle the event.
When you drop something in the inner Canvas, it handles it, but doesn't stop the event propagation, so the parent element (the containing Canvas) will handle it again.
In the overriden OnDrop method you have a DragEventArgs parameter.
Set its Handled property to true if you want to prevent containing elements to handle the same event.
Example
I created a simple derived Canvas as well:
public class MyCanvas : Canvas
{
public static readonly DependencyProperty StopDropPropagationProperty = DependencyProperty.Register(
"StopDropPropagation", typeof (bool), typeof (MyCanvas), new PropertyMetadata(default(bool)));
public bool StopDropPropagation
{
get { return (bool) GetValue(StopDropPropagationProperty); }
set { SetValue(StopDropPropagationProperty, value); }
}
protected override void OnDrop(DragEventArgs e)
{
Children.Add(new TextBlock
{
Text = "Dropped here"
});
e.Handled = StopDropPropagation;
}
}
In the view I've put two of these, one nested inside the other.
<local:MyCanvas AllowDrop="True" Background="DarkGray">
<local:MyCanvas Width="300" Height="300" Canvas.Left="100" Canvas.Top="10" Background="BurlyWood"
StopDropPropagation="True"></local:MyCanvas>
</local:MyCanvas>
The thing to note is that I added the new DependencyProperty called StopDropPropagation and set it to true in the inner Canvas. That will set the Handled to true.
So when I drop something in the inner Canvas the TextBlock will only be added in that element:

Related

Add Control to User Control

I created a user control with WPF which is supposed to take in a list of controls as its content.
I succeeded in creating such a user control, but have run into the issue that I cannot name any of the sub-controls I added.
Here's the code for the user control:
public static DependencyProperty InnerContentProperty = DependencyProperty.Register("TaskListItems",
typeof(UIElement), typeof(TaskList));
public UIElement TaskListItems {
get { return (UIElement)GetValue(InnerContentProperty); }
set { SetValue(InnerContentProperty, value); }
}
And the XAML (relevant part):
<ScrollViewer>
<ContentControl Content="{Binding TaskListItems, RelativeSource={RelativeSource AncestorType={x:Type local:TaskList}}}"/>
</ScrollViewer>
The implementation then looks like so:
<uc:TaskList Grid.Column="0" HeaderText="Daily Task List" FooterText="Completed: 10">
<uc:TaskList.TaskListItems>
<StackPanel>
<Button Content="Some Button"/>
<Button Content="Some Button 2"/>
</StackPanel>
</uc:TaskList.TaskListItems>
</uc:TaskList>
So when I add a control to my user control such as a button for example, everything works fine, except I cannot give the button a name and I get the error:
Cannot set Name attribute value 'test' on element 'Button'. 'Button' is under the scope of element 'TaskList', which already had a name registered when it was defined in another scope
So my question is, is there a way to functionally do the same thing here, but also letting me give my controls names?
Edit for more information:
What I'm going for is a user control that can take in a list of other user controls I created.
Those "list item" controls have four buttons each (the text bit is also a button):
The text button just opens a window with info about the selected task.
The check mark button is supposed to create an event to request the tasks completion.
The vertical ellipsis button opens a window where you can modify the task.
Lastly the x button is supposed to create an event to request cancellation/removal of the selected task.
Issue lies with creating the events and subscribing to them. What I have so far are two event handlers which are invoked in each of the buttons click events.
public event EventHandler<TaskItemEventArgs> TaskCompletedEvent;
public event EventHandler<TaskItemEventArgs> TaskRemoveRequestEvent;
private void acceptBtn_click(object sender, RoutedEventArgs e)
{
TaskCompletedEvent?.Invoke(this, new TaskItemEventArgs() { EventText = "Task Completed!" });
}
private void removeBtn_click(object sender, RoutedEventArgs e)
{
TaskRemoveRequestEvent?.Invoke(this, new TaskItemEventArgs() { EventText = "Task Remove Requested!" });
}
The problem now is that I don't know where to subscribe these events to in order to modify the "task item" control that invoked the event. I was thinking I should simply add it in the parent control, but I wouldn't know how to go about that, as all the controls here are dynamic.
Now that I think about it, I guess you wouldn't need a control name for the above task. The dynamic controls have me confused though.
I actually got the code to work now after a bit of experimentation and googling. I dynamically create a stack panel in the TaskList control and when I hit the "plus" button in the top right corner, a dialog opens allowing me to describe my task. After that I dynamically create a TaskListItem and subscribe to it's events with some methods I created in TaskList. Finally I add the TaskListItem to the StackPanel and if I want to modify it I can just modify the specifc object that asked to be edited. Works pretty well.

How to find the Parent of a CustomControl from inside the CustomControl

I have a class which extends Canvas in WPF.
This class is placed in a ScrollViewer.
Without passing a specific reference to the ScrollViewer into the Canvas, I want to find the ScrollViewer which contains the Canvas from within the Canvas itself.
The Parent property of the class which extends Canvas is null, and every attempt to use the VisualTreeHelper just returns null as well.
I have attempted to find the visual ancestor using VisualTreeHelper.GetParent(this), however the parent property is null.
As the ExtendedCanvas will be used in multiple instances, I would like it to be able to resolve its containing ScrollViewer without the need to specifically reference the ScrollViewer in either code behind or in XAML.
I realise that I could add a dependency propery in the ExtendedCanvas and create a binding in the XAML, however I would like the component to work by simply dropping it into a container.
Similarly, I would not be averse to placing the ScrollViewer onto a panel of some sort, then placing my ExtendedCanvas within it, so that my component uses that panel as its lowermost containing element.
What is puzzling me is that as I understand it, the VisualTreeHelper will navigate the entire visual tree for the running application. It seems that either my assumption is entirely wrong, or it can only navigate downwards from the specified component.
Is this possible to achieve without the above approaches?
Example Code:
cs -
public class ExtendedCanvas:Canvas {
//I wish to automatically populate this scroll viewer
//reference to the instance of the scrollviwer which contains
//this ExtendedCanvas instance
private ScrollViewer _containingScrollViewer = null;
}
xaml -
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" >
<local:ExtendedCanvas x:Name="extendedCanvas" />
</ScrollViewer>
You can find its parent like this:
public ExtendedCanvas()
{
//it hasn't been added to its parent yet
Loaded += ExtendedCanvas_Loaded;
}
private void ExtendedCanvas_Loaded(object sender, RoutedEventArgs e)
{
//now it is added to its parent
_containingScrollViewer = Parent as ScrollViewer;
}

Focus abstraction

UserControl with buttons (some of them are disabled) is nested inside other UserControl. There are several of such displayed in the window at once.
Now I need to set focus to first enabled button of nested UserControl, while the logic to choose focus will run on the level of window (e.g. when window will enable certain UserControl).
I need to be able to pass that focus request (via properties?) through several ViewModels and finally trigger it in the View of nested UserControl.
Ho can I abstract focus request? E.g. I want to be able to tell "set focus to this high level UserControl" and that should somehow automatically go through nested UserControl and its buttons, because only button is the element what can receive focus.
Pseudo-code:
// in window
UserControlA.Focus();
// should in fact set focus to 4th button of nested user control
UserControlA.UserControlB.ButtonD.Focus();
// because of data templates it is actually more like this
var nested = UserControlA.ContentControl.Content as UserControlB;
var firstEnabledButton = nested.ItemsControl[3] as Button;
firstEnabledButton.SetFocus();
// and because of MVVM it may be as simple as
ViewModelA.IsFocused = true;
// but then A should run
ViewModelB.IsFocused = true;
// and then B should set property of button ViewModel
Buttons.First(o => o.IsEnabled).IsFocused = true.
// and then this has to be somehow used by the view (UserControlB) to set focus...
Problem is not with how to set focus in MVVM, this can be done somehow (with triggers it needs ugly workaround where property is first set to false). My problem is how to pass that request ("and then ..., and then ..., and then..." in example above).
Any ideas?
I am looking for a simple and intuitive xaml solution with the most reusability. I don't want to spam every ViewModel and views with ...IsFocused properties and bindings.
I can use some side effect to my advantage, e.g. consider this behavior
public static bool GetFocusWhenEnabled(DependencyObject obj) => (bool)obj.GetValue(FocusWhenEnabledProperty);
public static void SetFocusWhenEnabled(DependencyObject obj, bool value) => obj.SetValue(FocusWhenEnabledProperty, value);
public static readonly DependencyProperty FocusWhenEnabledProperty =
DependencyProperty.RegisterAttached("FocusWhenEnabled", typeof(bool), typeof(FocusBehavior), new PropertyMetadata(false, (d, e) =>
{
var element = d as UIElement;
if (element == null)
throw new ArgumentException("Only used with UIElement");
if ((bool)e.NewValue)
element.IsEnabledChanged += FocusWhenEnabled_IsEnabledChanged;
else
element.IsEnabledChanged -= FocusWhenEnabled_IsEnabledChanged;
}));
static void FocusWhenEnabled_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var element = (UIElement)sender;
if (element.IsEnabled)
element.Dispatcher.InvokeAsync(() => element.Focus()); // invoke is a must
}
which can be used to automatically focus enabled element. This require some IsEnabled logic in addition and will easily stop working in some complicated scenarios (where enabling should not cause the focusing).
I am thinking if I can add some attached property to pass focus requests all the way through xaml (using only xaml) when attempting to set focus to container, which is not focusable.
I think you should consider using the FrameworkElement.MoveFocus method together with FocusNavigationDirection.Next - this should in general give you the expected result, i.e. give focus to the first encountered control which can receive keyboard focus. In particular that means that non-focusable controls, disabled controls, and controls that cannot receive keyboard focus (such as ItemsControl, UserControl etc.) will be omitted. The only catch here is that the controls will be traversed in tab order, but unless you're messing around with that it should traverse the visual tree in depth-first pre-order manner. So this code:
UserControlA.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
should give focus to UserControlA.UserControlB.ButtonD if it is the first keyboard-focusable and enabled descendant of UserControlA.
In terms of dismissing the necessity to use code-behind what I'd do is the following. First of all I'd drop using view-model properties to control focus. Moving focus seems to me a lot more like request-based concept rather than state-based, so I'd use events (e.g. FocusRequested) instead. To make it reusable I'd create a one-event interface (e.g. IRequestFocus). The final touch would be to create a behavior that would automatically inspect if DataContext of the attached object implements IRequestFocus and call MoveFocus each time the FocusRequested event is raised.
With such setup all you'd need to do is to implement IRequestFocus in ViewModelA, and attach the behavior to UserControlA. Then simply raising the FocusRequested in ViewModelA would result in moving focus to UserControlA.UserControlB.ButtonD.

Bug in UWP or am I missing something

I'm starting my first Universal Windows app. First thing I wanted was to subclass the main "Page" class for navigation. For simple purposes, I wanted to just add a RightTapped event hook to display a message of the actual page displayed...
Anyhow, I created a brand new project. Created a single class MyPage
public class MyPage : Page
{
public MyPage()
{
RightTapped += MyPage_RightTapped;
}
private async void MyPage_RightTapped(object sender, RightTappedRoutedEventArgs e)
{
var dialog = new MessageDialog("This page is " + GetType(), "What is my form");
await dialog.ShowAsync();
}
}
Then on the default main form, I changed MainPage.xaml from
<Page
to
<local:MyPage
In the codebehind, I changed
public sealed partial class MainPage : Page
to
public sealed partial class MainPage
Run the form, it works, right-click on keyboard and message comes up.
Now the problem. In the main page, at the Grid declaration, it is define with a background...
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
If I remove this color (the actual color default is "#FFFFFFFF")
<Grid>
the RightTapped even no longer works and this is the ONLY change. I put in a background of any other color and RightTapped works.
Can anyone explain this and why it fails without background color which should not have any bearing vs working with a background?
This sounds like it's documented behavior. The documentation for UIElement.RightTapped contains some relevant hints:
For touch actions and also for interaction-specific or manipulation events that are consequences of a touch action, an element must be hit-test visible in order to be the event source and fire the event that is associated with the action. UIElement.Visibility must be Visible. Other properties of derived types also affect hit-test visibility. For more info, see Events and routed events overview.
And the details from Events and routed events overview: Hit testing and input events:
There are several factors that affect hit testing, but you can determine whether a given element can fire input events by checking its IsHitTestVisible property. This property returns true only if the element meets these criteria:
The element's Visibility property value is Visible.
The element's Background or Fill property value is not null. A nullBrush value results in transparency and hit test invisibility. (To make an element transparent but also hit testable, use a Transparent brush instead of null.)
The Grid's Background property (inherited from Panel) defaults to null, making the Grid without a Background XAML attribute invisible to hit-testing.

C#: Windows Forms: Getting keystrokes in a panel/picturebox?

I'm making a level editor for a game using windows forms. The form has several drop down menus, text boxes, etc, where the user can type information.
I want to make commands like CTRL + V or CTRL + A available for working within the game world itself, not text manipulation. The game world is represented by a PictureBox contained in a Panel.
This event handler isn't ever firing:
private System.Windows.Forms.Panel canvas;
// ...
this.canvas = new System.Windows.Forms.Panel();
// ...
this.canvas.PreviewKeyDown += new System.Windows.Forms.PreviewKeyDownEventHandler(this.canvas_PreviewKeyDown);
What is the preferred way of doing this? Can a panel even receive keyboard input? I would like to allow the user to use copy/paste/select-all commands when working with the text input, but not when placing objects in the game world.
From the MSDN documentation for Control.CanSelect:
The Windows Forms controls in the
following list are not selectable and
will return a value of false for the
CanSelect property. Controls derived
from these controls are also not
selectable.
Panel
GroupBox
PictureBox
ProgressBar
Splitter
Label
LinkLabel (when there is no link present in the control)
Although it says controls derived from these controls cannot receive focus, you can create a derived control and use the SetStyle method to enable the "Selectable" style. You also must set the TabStop property to true in order for this to work.
public class SelectablePanel : Panel
{
public SelectablePanel()
{
this.SetStyle(ControlStyles.Selectable, true);
this.TabStop = true;
}
}
Then use this control instead of the normal Panel. You can handle the PreviewKeyDown event as intended.
You'll probably want to do the key capture at the form level. This is highly recommended reading from the person who helped write the underlying .NET code:
http://blogs.msdn.com/jfoscoding/archive/2005/01/24/359334.aspx

Categories

Resources