I need help in unifying size of elements in stackpanel
void MainPageLoaded(object sender, RoutedEventArgs e)
{
var random = new Random();
for (var i = 0; i < 5; i++)
{
var grid = new Grid();
var border = new Border()
{
Height = random.Next(50, 150),
Width = random.Next(50, 150),
Margin = new Thickness(10),
BorderBrush = new SolidColorBrush(Colors.White),
BorderThickness = new Thickness(1)
};
grid.Children.Add(border);
imageBoxesStackPanel.Children.Add(grid);
}
var h = imageBoxesStackPanel.Children.Max(n => n.DesiredSize.Height);
what I am trying to achieve is to find max height and max width of each grid in stackpanel and apply it to all of them. The problem is that desired size is always wrong.
You can only do this in a custom way after a measure/arrange pass, before that the sizes won't be visible.
After that (in the OnLoaded event, which you have), you can use the ActualHeight and ActualWidth of the grids.
In short:
var h = imageBoxesStackPanel.Children.Max(n => n.ActualHeight);
This is however bad for performance and will trigger another layout pass.
Remarks:
In WPF the best solution would be a SharedSizeGroup or a UniformGrid. This is not implemented in Silverlight, but there are people who have implemented it.
In WPF there is the UniformGrid to do this job, but unfortunately it's not implemented for Silverlight by default. There are several alternatives for it, e.g. this one
Related
I have a question regarding WPF.
I have a Canvas that serves as a visual editor! I have a few 'nodes' positioned in the Canvas using 'X' and 'Y' properties (Canvas.Left and Canvas.Top). Now, I need this Canvas to let the user Zoom (in & out) and Pan around, as he want's to.
I implemented kind of a hack to emulate that behavior. This is the Code that let's the user 'pan' around in the Canvas:
///In file MainWindow.xaml.cs
private void ZoomPanCanvas_MouseMove(object sender, MouseEventArgs e) {
if (IsMouseDown) {
///Change the Cursor to Scroll
if (mNetworkUI.Cursor != Cursors.ScrollAll)
mNetworkUI.Cursor = Cursors.ScrollAll;
var currPosition = e.GetPosition(mNetworkUI);
var diff = currPosition - MouseLastPosition;
var p = new Point(diff.X, diff.Y);
mNetworkUI.ViewModel.Network.SetTransformOffset(p);
MouseLastPosition = currPosition;
}
}
///In file NetworkViewModel.cs
public void SetTransformOffset(Point newOffset) {
for (int i = 0; i < Nodes.Count; i++) {
Nodes[i].X += newOffset.X;
Nodes[i].Y += newOffset.Y;
}
}
Where the 'Nodes' are my editor-nodes displayed in the Canvas. The Zooming (with respect to the mouse position works as follows:
///File MainWindow.xaml.cs
private void ZoomPanCanvas_MouseWheel(object sender, MouseWheelEventArgs e) {
///Determine the Scaling Factor and Scale the Rule-Editor
var factor = (e.Delta > 0) ? (1.1) : (1 / 1.1);
currrentScale = factor * currrentScale;
ScaleNetwork();
///Translate the Nodes to the desired Positions
var pos = e.GetPosition(mNetworkUI);
var transform = new ScaleTransform(factor, factor, pos.X, pos.Y);
var offSet = new Point(transform.Value.OffsetX, transform.Value.OffsetY);
mNetworkUI.ViewModel.Network.SetTransformOffset(offSet);
}
///Also in MainWindow.xaml.cs
private void ScaleNetwork() {
mNetworkUI.RenderTransform = new ScaleTransform(currrentScale, currrentScale);
mNetworkUI.Width = ZoomPanCanvas.ActualWidth / currrentScale;
mNetworkUI.Height = ZoomPanCanvas.ActualHeight / currrentScale;
}
So, in the 'panning' I calculate the difference to the last mouse position and use that vector to manipulate the nodes, not the Canvas itself.
When I zoom, I determine the new zoom, set a new RenderTransform, resize the Canvas to again fill the provided space and again re-position the nodes in the Canvas.
It works very well for now. I can 'pan & zoom' around how I want, but I realized, that with many nodes present in my 'network' (connected nodes), things get quite slow.
One reason is, that on every movement of a node some events are raised resulting in a noticable delay when panning.
How is such a thing (without fixed Canvas-size and Scrollbars) possible in a performant manner? Is there a control out there that I can use? Is this possible with the Extended WPF toolkit's ZoomBox control?
Thank you!
I've written a Viewport control for this exact functionality.
I've also packaged this up on nuget
PM > Install-Package Han.Wpf.ViewportControl
It extends a ContentControl which can contain any FrameworkElement and provides constrained zoom and pan functionality. Just make sure to add Generic.xaml to your app.xaml
<Application.Resources>
<ResourceDictionary Source="pack://application:,,,/Han.Wpf.ViewportControl;component/Themes/Generic.xaml" />
</Application.Resources>
Usage:
<Grid width="1200" height="1200">
<Button />
</Grid>
The source code for the control and theme is on my gist and can be found on my github along with a demo application that loads an image into the viewport control.
Im working on flowchart kind of application in asp.net using silverlight.. Im a beginner in Silvelight, Creating the elements (Rectangle,Ellipse,Line.. ) dynamically using SHAPE and LINE Objects in codebehind (c#)
These shapes will be generated dynamically, meaning I'll be calling a Web service on the backend to determine how many objects/shapes need to be created. Once this is determined, I'll need to have the objects/shapes connected together.
how to connect dynamically created shapes with a line in Silverlight like a flowchart.
I read the below article, but its not working for me, actualHeight & actualWidth of shapes values are 0.
Connecting two shapes together, Silverlight 2
here is my MainPage.xaml
<UserControl x:Class="LightTest1.MainPage">
<Canvas x:Name="LayoutRoot" Background="White">
<Canvas x:Name="MyCanvas" Background="Red"></Canvas>
<Button x:Name="btnPush" Content="AddRectangle" Height="20" Width="80" Margin="12,268,348,12" Click="btnPush_Click"></Button>
</Canvas>
code behind MainPage.xaml.cs
StackPanel sp1 = new StackPanel();
public MainPage()
{
InitializeComponent();
sp1.Orientation = Orientation.Vertical;
MyCanvas.Children.Add(sp1);
}
Rectangle rect1;
Rectangle rect2;
Line line1;
private void btnPush_Click(object sender, RoutedEventArgs e)
{
rect1 = new Rectangle()
{
Height = 30,
Width = 30,
StrokeThickness = 3,
Stroke = new SolidColorBrush(Colors.Red),
};
sp1.Children.Add(rect1);
rect2 = new Rectangle()
{
Height = 30,
Width = 30,
StrokeThickness = 3,
Stroke = new SolidColorBrush(Colors.Red),
};
sp1.Children.Add(rect2);
connectShapes(rect1, rect2);
}
private void connectShapes(Shape s1, Shape s2)
{
var transform1 = s1.TransformToVisual(s1.Parent as UIElement);
var transform2 = s2.TransformToVisual(s2.Parent as UIElement);
var lineGeometry = new LineGeometry()
{
StartPoint = transform1.Transform(new Point(1, s1.ActualHeight / 2.0)),
EndPoint = transform2.Transform(new Point(s2.ActualWidth, s2.ActualHeight / 2.0))
};
var path = new Path()
{
Data = lineGeometry,
Stroke = new SolidColorBrush(Colors.Green),
};
sp1.Children.Add(path);
}
what I am doing in button click event is just adding two rectangle shapes and tring to connect them with a line (like flowchart).
Please suggest what is wrong in my code..
Try replacing the line
connectShapes(rect1, rect2);
with
Dispatcher.BeginInvoke(() => connectShapes(rect1, rect2));
I'm not sure of the exact reason why this works, but I believe the shapes are only rendered once control passes out of your code, and only once they are rendered do the ActualWidth and ActualHeight properties have a useful value. Calling Dispatcher.BeginInvoke calls your code a short time later; in fact, you may notice the lines being drawn slightly after the rectangles.
The TransformToVisual method behaves in much the same way as the ActualWidth and ActualHeight properties. It will return an identity transformation if the shape hasn't been rendered. Even if your lines were being drawn with a definite width and height, they would end up being drawn all on top of one another at the top-left.
I also found that I needed to add the lines to the Canvas, not the StackPanel, in order for them to be drawn over the rectangles. Otherwise the StackPanel quickly filled up with lines with a lot of space above them.
I have set a Grid's background brush as an ImageBrush.
But when I set the Grid's FlowDirection to RightToLeft, the image is flipped horizontically.
Is it possible to (un)flip the grid background ImageBrush using a certain Transition or any other way?
Not much you can do about that with sensible means (there same means that are far from sensible).
Instead place an Image element as the first item in the Grid with Grid.RowSpan, Grid.ColumnSpan to cover all the cells. Use Stretch="Fill" on the Image since thats how a background typically behaves.
Well, i do understand that my comment is outdated, but this question is popping up one of the first in Google search, so here is my solution:
I was localizing the application for the right-to-left culture. The simple decision to set FlowDirection=RTL comes with unexpected drawbacks like the background containing the company logo is flipped. I have applied the matrix transformation for the image brush used to render the background:
var mbgBrush = TryFindResource("MainBackground") as Brush;
if (mbgBrush == null) return null;
if (FlowDirection == FlowDirection.LeftToRight) return mbgBrush;
var mainBgImageBrush = mbgBrush as ImageBrush;
if (mainBgImageBrush == null) return mbgBrush;
var flipXaxis = new MatrixTransform(-1.0, 0, 0, 1.0, 1, 0);
var flippedBrush = new ImageBrush
{
Stretch = Stretch.None,
Opacity = 1.0,
ImageSource = mainBgImageBrush.ImageSource,
RelativeTransform = flipXaxis
};
return flippedBrush;
The problem: I am not getting a textbox setting that will have a horizontally wordwrap and vertically auto grow functionality. I wish to do that by writing a code. I have written following code that creates a text box at mouse dblclick with wordwrap:
TextBox text2 = new TextBox();
text2.Width = 500;
text2.Visibility = Visibility.Visible;
text2.Focus();
text2.Height = 30;
text2.HorizontalAlignment = HorizontalAlignment.Left;
text2.VerticalAlignment = VerticalAlignment.Top;
Point p = e.GetPosition(LayoutRoot);
text2.Margin = new Thickness(p.X, p.Y, 0, 0);
LayoutRoot.Children.Add(text2);
But, textbox does not grow vertically.
Can somebody suggest me a code in C# to do exactly what I desire?
try using this
Grid grid = new Grid();
grid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
grid.RowDefinitions.Add(new RowDefinition());
TextBox textBox = new TextBox() { Width = 100, TextWrapping = TextWrapping.Wrap };
textBox.SetValue(Grid.RowProperty, 0);
grid.Children.Add(textBox);
window.Content = grid;
where window is the Name assigned to Window(root).
One way to accomplish the growth you're looking for is to use a string measuring mechanism which you would run any time the text in your text box changes. Simply measure and resize your text box accordingly with any change to the contents.
Have you tried this?
text2.Height = double.NaN; // or don't set the property, but some custom styles might give a default value ..
text2.TextWrapping = TextWrapping.Wrap;
text2.MinHeight = 30; // or not if you want the styles default
instead of
text2.Height = 30;
not setting it or using double.NaN is the same as using 'Auto' in xaml.
I a grid on my silverlight control, I am programatically adding a canvas, and in the canvas I am loading and displaying Image.
I'm also adding a rotation to the canvas. The problem is that by default the CenterX and CenterY of the rotation is the top left of the canvas. What I want is the rotation to happen around the centre of the canvas.
To do this, I've tried setting the CenterX and CenterY of the Rotation to the Images ActualWidth / 2 and ActualHeight / 2, however I've discovered that ActualWidth and ActualHeight are not always populated, at least not right away. How can I force them to get updated?
Even using the DownloadProgress event on the image doesn't seem to guarantee the ActualWidth and ActualHeight are populated, and neither does using this.Dispatcher.BeginInvoke()...
Image imgTest = new Image();
Canvas cnvTest = new Canvas();
Uri uriImage = new Uri("myurl", UriKind.RelativeOrAbsolute);
System.Windows.Media.Imaging.BitmapImage bmpDisplay = new System.Windows.Media.Imaging.BitmapImage(uriImage);
bmpDisplay.DownloadProgress += new EventHandler<System.Windows.Media.Imaging.DownloadProgressEventArgs>(this.GetActualDimensionsAfterDownload);
imgTest.Source = bmpDisplay;
imgTest.Stretch = Stretch.Uniform;
imgTest.HorizontalAlignment = HorizontalAlignment.Center;
imgTest.VerticalAlignment = VerticalAlignment.Center;
cnvTest.Children.Add(imgTest);
this.grdLayout.Children.Add(imgTest);
this.Dispatcher.BeginInvoke(new Action(GetActualDimensions));
To update the ActualWidth and ActualHeight of a FrameworkElement you will have to call UpdateLayout.
Unfortunately, calling updateLayout doesn't always work either depending on your situation.
I've had better luck doing something like:
whateverUIElement.Dispatcher.BeginInvoke(()
{
//code that needs width/height here
}
);
but even that fails too often.
Most reliable method I found is to use DependencyPropertyDescriptor AddValueChanged listeners of ActualWidth and ActualHeight instead of OnLayoutUpdated to get element sizes after rendering
DependencyPropertyDescriptor descriptor = DependencyPropertyDescriptor.FromProperty(ActualWidthProperty, typeof(StackPanel));
if (descriptor != null)
{
descriptor.AddValueChanged(uiPanelRoot, DrawPipelines_LayoutUpdated);
}
descriptor = DependencyPropertyDescriptor.FromProperty(ActualHeightProperty, typeof(StackPanel));
if (descriptor != null)
{
descriptor.AddValueChanged(uiPanelRoot, DrawPipelines_LayoutUpdated);
}
void DrawPipelines_LayoutUpdated(object sender, EventArgs e)
{
// Point point1 = elementInstrumentSampleVial.TranslatePoint(
// new Point(11.0, 15.0), uiGridMainInner);
}
Instead of using StackPanel, Grid etc. use base element that you are depending on for relative sizes