WPF hit testing a rectangular area - wpf

I have a WrapPanel containing an arbitrary number of jagged sized elements. I'd like to implement drag select for my items.
It seems pretty obvious how to HitTest for a point, but how can I find all items within a rectangular area?

You may use VisualTreeHelper.HitTest with a GeometryHitTestParameters argument and a HitTestFilterCallback that checks if a Visual is a direct child of the Panel.
Something like this:
var selectedElements = new List<DependencyObject>();
var rect = new RectangleGeometry(...);
var hitTestParams = new GeometryHitTestParameters(rect);
var resultCallback = new HitTestResultCallback(
result => HitTestResultBehavior.Continue);
var filterCallback = new HitTestFilterCallback(
element =>
{
if (VisualTreeHelper.GetParent(element) == panel)
{
selectedElements.Add(element);
}
return HitTestFilterBehavior.Continue;
});
VisualTreeHelper.HitTest(
panel, filterCallback, resultCallback, hitTestParams);
It looks a little complicated, but the HitTestFilterCallback is necessary to get all Visuals in the visual tree, not only those that actually got hit. For example if your panel contains Label controls, the HitTestResultCallback will only be called for the Border and TextBlock child Visuals of each Label.

The option for controlling hit test visibility is the IsHitTestVisible property. This property allows you to control hit test visibility regardless of the brush with which the UIElement is rendered.
Also, You want to set the Fill to Transperent
<Rectangle Width="200" Height="200" Margin="170,23,12,35" Fill="Transparent" IsHitTestVisible="True" />

Related

Stackpanel Visibility - what am I doing wrong . .

I want to make a ListBox containing StackPanels as its elements. The StackPanels will be created and added at runtime, in the C# code behind. .
The StackPanels will contain some images but at the moment none of the image stuff exists yet, so in this code I just wanted to make sure I could do the mechanics.
My XAML looks like this:
<Grid>
<ListBox Name="listBoxImages" BorderBrush="DarkGray" Width="600" Height="300" BorderThickness="3"
Margin="0" Padding="0" Background="#FFC0C0C0"/>
</Grid>
In the C# code-behind I deliberately set a background color of the Listbox different from the one in the XAML to verify I was accessing the ListBox properly in the code-behind.
listBoxImages.Background = Brushes.Blue; //just to show I'm accessing it . . .
That part works; the ListBox displays blue.
Then I went to add a StackPanel. Since there's nothing in it yet I gave it a height and width and a different background color, but I don't see anything. So I checked its visibility and it's false. So I tried setting the visibility using System.Windows.Visibility.Visible but it's still false after that.
StackPanel myStackPanel = new StackPanel();
myStackPanel.HorizontalAlignment = HorizontalAlignment.Left;
myStackPanel.VerticalAlignment = VerticalAlignment.Top;
myStackPanel.Background = Brushes.Bisque; // make something visible
myStackPanel.MinHeight = 50;
myStackPanel.Width = 50;
bool bResult = myStackPanel.IsVisible;
myStackPanel.Visibility = System.Windows.Visibility.Visible;
bResult = myStackPanel.IsVisible;
myStackPanel.Margin = new Thickness(10);
listBoxImages.Items.Add(myStackPanel);
Why is the StackPanel visibility false and is that the reason why I don't see it after adding it to the ListBox? (I'm sorry if this is a noob question)
IsVisible is set to true when it gets rendered on UI.
You can verify by hooking to Loaded event and see value of IsVisible in it by putting breakpoint on the handler.
myStackPanel.Loaded += (s, e) => bResult = myStackPanel.IsVisible;
Also, I verified with your posted code and can see StackPanel rendered on UI.
More verbose definition:
.........
listBoxImages.Items.Add(myStackPanel);
myStackPanel.Loaded += new RoutedEventHandler(myStackPanel_Loaded);
}
void myStackPanel_Loaded(object sender, RoutedEventArgs e)
{
bool isVisible = (sender as StackPanel).IsVisible;
}
Listbox is better populated with an items template. If you want to add arbitrary controls of different types, just use a stack panel.

How to Update UserControl (Chart) in MVVM

I have a chart embedded in a usercontrol
PlotControl.xaml (PlotControl.xaml.cs)
This plot control is used on a View in MVVM.
NOW! when I update the UI of PlotControl (e.g. I draw vertical and horizontal markers on chart), these updates are not visible on View (unless I do double click on the View or do a Minimize-Maximize window).
Is there a way the updated UI is updated automatically on View?
View code looks like:
<Grid Margin="4">
<nms:PlotControl x:Name="PlotControl" Margin="10,10" DockPanel.Dock="Right" />
</nms:PlotControl>
Snippet from PlotControl.xaml.cs code looks like:
ChartPanel cpnl = new ChartPanel();
chart.View.Layers.Add(cpnl);
ChartMarker rightVerticalMarker = new ChartMarker(chart, MarketType.RightVertical);
rightVerticalMarker.DataPoint = new Point(30, double.NaN);
cpnl.Children.Add(rightVerticalMarker);
cpnl.UpdateLayout();
chart.UpdateLayout();
ChartMarker is simply a line Horizontal (or Vertical) defined by enum MarketType.
NOTE: I have been searching for this problem since two days in SO, but nowhere could I find the solution.
Updates can be forced by Arrange methods. Here is what I have found, which solved my problem.
Size userControlSize = this.RenderSize; //original size of userControl
Size chartSize = chart.RenderSize; // original size of chart
chart.Arrange(chart.View.PlotRect); // forcing layout elements on chart
chart.Arrange(new Rect(chartSize)); // setting the chart size to original value
Arrange(new Rect(userControlSize)); // setting the userControl size to original value

WPF ContextMenu placement adjusted event

Does anyone know of how I can determine when the ContextMenu get its placement automatically adjusted due to being too close to the edge of the screen?
My scenario is that I have a ContextMenu that has 2 rounded corners and 2 square corners. When the menu opens down I round the bottom 2, and if the menu is opening upwards then I round the top 2. The problem is that I haven't found an event or property to bind to that tells me when the menu gets its direction automatically changed.
Here's some simplified sample code to try out. If you click when the window is at top of screen then menu goes down. If you move window to bottom of screen then the menu will go up.
<Window x:Class="menuRedirection.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="100" Width="200">
<DockPanel Name="panel" ContextMenuOpening="DockPanel_ContextMenuOpening">
<DockPanel.ContextMenu>
<ContextMenu>
<MenuItem Header="item"/>
<MenuItem Header="item"/>
<MenuItem Header="item"/>
<MenuItem Header="item"/>
</ContextMenu>
</DockPanel.ContextMenu>
<Rectangle DockPanel.Dock="Bottom" Name="menuTarget" Fill="Red" Height="10"/>
<TextBlock DockPanel.Dock="Top" Text="right click for context menu"/>
</DockPanel>
</Window>
private void DockPanel_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
ContextMenuService.SetPlacement(panel, PlacementMode.Bottom);
ContextMenuService.SetPlacementTarget(panel, menuTarget);
}
Here's what the real application looks like so you can see my problem with needing to know to adjust my rounded corners.
As far as I can tell, this is not possible.
Using JustDecompile, I traced this functionality to the UpdatePosition method in the Popup class. The final location seems to be set here:
this._positionInfo.X = num4;
this._positionInfo.Y = num5;
this._secHelper.SetPopupPos(true, num4, num5, false, 0, 0);
_secHelper is a helper class of type PopupSecurityHelper, and seems to just be an internal helper...And, none of these result in an event or even a public property being changed.
Here is an MSDN article explaining how popup positioning is determined in general (The 'When the popup encounters the edge of a screen' describes your scenario).
However, this article explains how you can use the CustomPopupPlacementCallback to override these behaviors somewhat. However, this still uses a PopupPrimaryAxis, which should flip the menu as necessary, and will result in the same problem.
The only other thing I could think of is that you could look into the PlacementRectangle and maybe poll the size and location similar to how UpdatePosition does things...or just check the popup itself just like UpdatePosition does.
This is a private method, though. So, any logic you try to mimic could change in a future version of the framework.
UPDATE
Also, you could possibly try bastardizing PointToScreen or PointFromScreen, but that would be very convoluted code if it worked...
I was unable to find a true WPF solution but Justin's comment lead me down the path of experimenting with comparing the menu's location with the PlacementTarget's location.
First step was to subscribe to the contextMenu.Loaded event (this fires after layout has been processed but before it's fully visible on the screen).
<ContextMenu ContextMenu.Loaded="ContextMenu_Loaded">
And then when that fires I can figure out if the menu was internally switched to the alternate placement for my requested placementMode. If it was reversed then I go ahead and adjust my rounded corners accordingly.
NOTE: i initially had used getWindowRect and compared the menu Rect with the target's Rect, but found that the menu Rect was always returning the prior instance's location. To avoid this problem I now get the relevant screen's workingArea and manually see if the menu fits.
NOTE2: be sure your menu's template results in the same window height for both inverted and regular display. Otherwise, your calculation could be off since getWindowRect returns the last menu's size.
void ContextMenu_Loaded(object sender, RoutedEventArgs e)
{
bool reversed = isMenuDirectionReversed(this.ContextMenu);
//existing styles are read-only so we have to make a clone to change a property
if (reversed)
{//round the top corners if the menu is travelling upward
Style newStyle = new Style(typeof(ContextMenu), this.ContextMenu.Style);
newStyle.Setters.Add(new Setter { Property = Border.CornerRadiusProperty, Value = new CornerRadius(10, 10, 0, 0) });
this.ContextMenu.Style = newStyle;
}
else
{ //since we may have overwritten the style in a previous evaluation,
//we also need to set the downward corners again
Style newStyle = new Style(typeof(ContextMenu), this.ContextMenu.Style);
newStyle.Setters.Add(new Setter { Property = Border.CornerRadiusProperty, Value = new CornerRadius(0, 0, 10, 10) });
this.ContextMenu.Style = newStyle;
}
}
Evaluation method:
private bool isMenuDirectionReversed(ContextMenu menu)
{
//get the window handles for the popup' placement target
IntPtr targetHwnd = (HwndSource.FromVisual(menu.PlacementTarget) as HwndSource).Handle;
//get the relevant screen
winFormsScreen screen = winFormsScreen.FromHandle(targetHwnd);
//get the actual point on screen (workingarea not taken into account)
FrameworkElement targetCtrl = menu.PlacementTarget as FrameworkElement;
Point targetLoc = targetCtrl.PointToScreen(new Point(0, 0));
//compute the location for the bottom of the target control
double targetBottom = targetLoc.Y + targetCtrl.ActualHeight;
if (menu.Placement != PlacementMode.Bottom)
throw new NotImplementedException("you need to implement your own logic for other modes");
return screen.WorkingArea.Bottom < targetBottom + menu.ActualHeight;
}
Final result:

In WPF how do I bind a Content Property of the UserControl to the internal control

I Have a user Control which Contains a ScrollPanel. And I want to bind the userControl's content property to the ScrollPanel.
So my xaml would look like:
<CustomControl>
<StackPanel/>
</CustomControl>
and in my UserControl my ScrollPanel child is set to StackPanel.
Do you mean ScrollViewer?
You have to remove the content from the user control (so the content no longer has a visual parent), then reassign the content to the scroll viewer.
In code:
var scrollViewer = new ScrollViewer();
var content = userControl.Content;
userControl.Content = null; // removes content from visual tree
scrollViewer.Content = content; // reassign content
If there's a way to do this via a binding, I haven't figured it out yet, though the situation where I'm having to do this is slightly different from yours.

Custom Control in Silverlight ListBox

I have a custom control I created from a expression design I created and exported to xaml. I have put in it a bound itemtemplate/datatemplate of a ListBox contorl. It doesn't seem to be rendering more than once and/or it is rendering each item in the same place(kind of like the same x,y coordinates.
It would seem to me that this should be a simple process. If I fill the datatemplate with a textblock it would generate a couple textblocks in a vertical list. I would expect if I swap out the textblock with my custom control I should get a couple custom controls in a vertical list.
Wouldn't this be teh expected behavior or is there a reason the listbox only appears to be rendering a single usercontrol? In both cases I use the same data for the listbox.
<telerik:ListBox x:Name="PeopleList" Grid.Row="1" >
<telerik:ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<custom:ExecSelector Height="100" Width="100" x:Name="ExecSelector" FullName="{Binding City}"></custom:ExecSelector>
</Grid>
</DataTemplate>
</telerik:ListBox.ItemTemplate>
</telerik:ListBox>
People = new List<PersonViewModel>();
PersonViewModel person2 = new PersonViewModel()
{
Name = "Austin Weise",
City = "Texas",
Email = "austin#build1.ca",
Position = "Techincal Director",
Bio = "Programmer"
};
PersonViewModel person = new PersonViewModel()
{
Name = "Ian House",
City = "Vancouver",
Email = "Ian#build1.ca",
Position = "Creative Director",
Bio = "Designer"
};
People.Add(person2);
People.Add(person);
PeopleList.DataContext = this;
PeopleList.ItemsSource = People;
That should provide enough to visualize it unless the UI elements are required for the custom control.
Based on your current description of your problem:
make sure you have not put any values on the Top and Left properties of the outer control.
make sure you have more than one data item in the list you are binding to
make sure your ListBox has sufficient height to be able to show more than one item
make sure you haven't disabled the ListBox scroll bar.
That's all the guesses that i can throw out there unless you have more details.

Resources