What is wrong with Canvas.SetTop(element,position) - wpf

I am trying to move and reposition elements with mouse and through code. But I guess I'm missing something or do something the wrong way. So I built a little sample app. It's just a empty wpf app with this MainWindow function
public MainWindow()
{
InitializeComponent();
Label lText = new Label();
lText.Content = "this is my test label";
lText.Height = 50;
lText.Width = 50;
lText.Background = Brushes.Aqua;
// do I really need to do this?
lText.VerticalAlignment = System.Windows.VerticalAlignment.Top;
lText.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
// this part already fails
Canvas.SetTop(lText, 20);
Canvas.SetLeft(lText, 10);
this.Content = lText;
}

The attached properties Canvas.Left and Canvas.Top (which in code behind are set by the Canvas.SetLeft and Canvas.SetTop methods) only have an effect for an element that is a direct child of a Canvas control.
So declare a Canvas as Content element in your MainWindow's XAML:
<Window ...>
<Canvas x:Name="canvas" />
</Window>
Then add the element to the Canvas' Children collection in code behind:
Canvas.SetTop(lText, 20);
Canvas.SetLeft(lText, 10);
canvas.Children.Add(lText);

The solution was that the Left and Top properties must be set inside the Xaml before you can read it. I was always getting NaN and therefor couldn't set the correct value either.

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.

WPF hit testing a rectangular area

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" />

Window sizing constraints by content

I want the window to respect MinWidth/MinHeight and MaxWidth/MaxHeight specifications of the content control inside.
Some suggested using SizeToContent, but this only helps to set the initial window size, not the constraints.
Others suggested overriding MeasureOverride and setting window's Min/Max height and width there, but this seems to be somewhat unclean, considering that such a trivial problem should surely have a purely declarative solution.
Just to mention another solution which seems reasonable but does not work (and had been previously mentioned in an answer which got deleted): binding MinWidth of the window to MinWidth of the control does not take into account window decorations.
If the initial window size is set so that actual content size is not coerced by the content's MinWidth/MinHeight and MaxWidth/MaxHeight in the initial layout pass (for example, by using Window.SizeToContent="WidthAndHeight"), then following equations are true:
Window.ActualSize - Content.ActualSize =
Window.MinSize - Content.MinSize = Window.MaxSize - Content.MaxSize.
Based on these equations you can derive the following code:
public MainWindow()
{
InitializeComponent();
this.SizeChanged += OnWindowSizeChanged;
}
private static void OnWindowSizeChanged(object sender, SizeChangedEventArgs e)
{
var window = (Window)sender;
var content = (FrameworkElement)window.Content;
window.MinWidth = window.ActualWidth - content.ActualWidth + content.MinWidth;
window.MaxWidth = window.ActualWidth - content.ActualWidth + content.MaxWidth;
window.MinHeight = window.ActualHeight - content.ActualHeight + content.MinHeight;
window.MaxHeight = window.ActualHeight - content.ActualHeight + content.MaxHeight;
window.SizeChanged -= OnWindowSizeChanged;
}
I do not know how to achieve this efficiently using the pure declarative approach since the code should be ran just once after the initial layout pass.
Some suggested using SizeToContent, but this only helps to set the initial window size, not the constraints.
I worked around this by setting the MinWidth and MinHeight properties right after the windows was initialized:
MainWindow.xaml
<Window ... SizeToContent="WidthAndHeight">
...
</Window>
MainWindow.xaml.cs
public MainWindow()
{
InitializeComponent();
SourceInitialized += (s, e) =>
{
MinWidth = ActualWidth;
MinHeight = ActualHeight;
};
}
Use Binding markup extension. A binding is wpf's way of saying when this property (source) changes update some other property (target). In this case the Grid's MinWidth property is the Source and your window's MinWidth property is the target.
<Window x:Class="MinMaxValuesOnWindows.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"
MinWidth="{Binding ElementName=gridy, Path=MinWidth}"
MinHeight="{Binding ElementName=gridy, Path=MinHeight}"
MaxWidth="{Binding ElementName=gridy, Path=MaxWidth}"
MaxHeight="{Binding ElementName=gridy, Path=MaxHeight}">
<Grid Name="gridy" MinHeight="80" MinWidth="80" MaxHeight="300" MaxWidth="300"/>
</Window>
As you mentioned in the topic this does not completely work, but you can use a converter on the binding to add on the window frame's height and width before updating the binding target (might require a PInvoke). Since I doubt the window frame thickness is dynamically changing in your application this can probably just be constant value (not necessarily true if user changes themes).

Silverlight/WPF: Retreiving the size of a UIElement once it has been rendered on screen

I have the following simple piece of code:
var canvas = new Canvas();
foreach (var ztring in strings)
{
var textblock = new TextBlock();
textblock.Text = ztring;
panel.Children.Add(textblock);
textblock.Measure(infiniteSize);
}
At this point I would expect any of the size properties (Height/Width, ActualHeight/ActualWidth, DesiredSize, RenderSize) to give me the size of the textblock. None of them do.
ActualHeight always gives 16.0 no matter what size font. ActualWidth changes according to the text length but not the font size.
I change the font size on the parent container and not the TextBlock itself.
I feel like I am missing some basic element of understanding the manipulation of silverlight elements from within the codebehind.
The question is: how do I get the real actual pixel size of my TextBlock?
Below is a sample that adds a TextBlock to a Canvas using code behind and once the TextBlock is rendered, it displays its height in the title of the window. Is that what you are looking for?
XAML:
<Window x:Class="HeightTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300">
<StackPanel TextBlock.FontSize="30">
<Canvas Name="_canvas" Height="200"/>
</StackPanel>
</Window>
Code behind:
using System.Windows;
using System.Windows.Controls;
namespace HeightTest
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
TextBlock textBlock = new TextBlock();
textBlock.Text = "Hello";
Canvas.SetLeft(textBlock, 25);
textBlock.Loaded +=
(sender, e) =>
{
Title = textBlock.ActualHeight.ToString();
};
_canvas.Children.Add(textBlock);
}
}
}
Have you tried using a real container like a Grid instead of Canvas?
What if you try reading the ActualSize property after the Measure using a Dispatcher.BeginInvoke?

Is it possible to start a video playing in a tooltip in Silverlight?

In Silverlight a tooltip can have as many elements in it as you want.
However, it doesn't receive focus so you can't have user interactivity in it.
Could you, though, start a video playing as soon as the tooltip opens and have the video stop as soon as the tooltip closes?
This is my first answer on Stack Overflow so I ask for your good humor.
I think you could run your video in the tooltip by using a video brush.
Here's some code I used to paint a fire video on the bar in the chart that represented heating with corn. ( long story) right here, you can see it is set to the fill of an ellipse.
#region video brush setup
protected void setupVideo()
{
VideoBrush _vb;
MediaElement mevideo;
_vb = new VideoBrush();
mevideo = new MediaElement();
mevideo.SetValue(Grid.NameProperty, "video");
Uri videoUri = new Uri("http://www.faxt.com/videos/ezburnboilerfire.wmv", UriKind.Absolute);
mevideo.Source = videoUri;
mevideo.Visibility = Visibility.Collapsed;
mevideo.MediaEnded += new RoutedEventHandler(me_MediaEnded);
MediaRoot.Children.Add(mevideo);
_vb.SetSource(mevideo);
Ellipse el = new Ellipse();
el.Width = 100;
el.Height = 100;
el.Fill = _vb;
MediaRoot.Children.Add(el);
}
You could do it with a VideoBrush as suggested by BPerreault, but you could also just set Tooltip.Content to a MediaElement.
That is because the Content property of Tooltip inherits from ContentControl and the Content property of a ContentControl can be any type of object, such as a string, a UIElement, or a DateTime. When Content is set to a UIElement (like MediaElement), the UIElement is displayed in the ContentControl. When Content is set to another type of object, a string representation of the object is displayed in the ContentControl. (from documentation)
It should be something like this:
<TextBlock x:Name="myText" Text="MouseOver and you'll get a ToolTip!">
<ToolTipService.ToolTip>
<MediaElement x:Name="myVideo" Source="Butterfly.wmv" Width="300" Height="300" />
</ToolTipService.ToolTip>
</TextBlock >

Resources