Exposing VisualOffsets property - c#

I am making an application in C# WPF and need the location of an object that I added to a panel. While debugging I found the VisualOffset property of the object that gives me the relative position. I just can't get the value from the code.
What I would like to do is (though it is not possible):
var display = new Display(); // This is a class that inhereit UserControl
.
.
// At some point when display is added to a panel
var position = display.VisualOffset; // This property is not accessible
So how can I get the relative position of an object?

Use TranslatePoint method of the Display instance. Set the parent control as a target. The code below will give you coordinates of display on its parent. If the container is further down the visual tree then you have to find a parent of a parent.
In my sample I'm finding that on the parent. I'm doing that on button click and then return the results as a string to a text box - purely for simplicity sake. But the idea is the same wherever you use it:
private void Button_Click(object sender, RoutedEventArgs e)
{
var parent = display.Parent as UIElement;
var location = display.TranslatePoint(new Point(0, 0), parent);
this.myTextBox.Text = $"x: {location.X}, y: {location.Y}";
}
display is of course an instance of the Display user control.

Related

How can I control the display order of child Forms in a MDI Form

When I use an MDI Form I have a problem. My source code just like this:
private void menuItem1_Click(object sender, EventArgs e)
{
Form[] charr = this.MdiChildren;
int i = 0;
foreach (Form chform in charr)
{
chform.Dock = DockStyle.Top;
}
this.LayoutMdi(System.Windows.Forms.MdiLayout.TileHorizontal);
}
The numbers of child Forms is more then 3. In order to display them correctly after the LayoutMdi() method is called, I had to set the Dock property of all child Forms to DockStyle.Top.
After calling LayoutMdi(MdiLayout.TileHorizontal), clicking the Title Bar of the first child Form, this child Form is displayed at the bottom of the MDI parent automatically.
I want that the clicked child Form maintains it's original position.
Is there any idea for this question?
Looking at the linked question - where it was suggested to set the Dock property to adjust the MDIChild Forms position - and the currently reported behavior, it is probably preferable to define the layout the MDIChild Forms without the help of automatic feature.
This allows to perform any layout logic that seems appropriate.
In the example, the MDIChildren.Height is calculated in relation to the MDIParent.ClientSize.Height and the number of opened MDIChildren, then multiplied by a values: in the sample code by 2, twice the base measure.
This Multiplier allows to define the Horizontal Tile Height of the MDICHildren quite precisely. Of course, you could implement some other logic that applies the multiplier only when there are at least 3 opened MDIChildren.
All the MDIChildren are re-sized to match the MDIParent.Width and the calculated Height, then ordered by Name and positioned from top to bottom.
Set different values of HorizontalTileHeightMultiplier to see how the MDIChildren are positioned in the MDIParent.ClientArea (MdiClient).
This multiplier could also be used as a custom Property in the Application, available to its Users allowing a custom tiling of the Forms.
The layout code is provided as a private method, so it can be easily used in different event handlers to perform/maintain the selected layout (the MDIParent.Resize, for example).
This method can also be easily adapted to replace the MdiLayout.TileVertical if required.
private float horizontalTileHeightMultiplier = 2;
private void menuItem1_Click(object sender, EventArgs e)
{
TileHorizontal()
}
private void TileHorizontal()
{
int openedForms = Application.OpenForms.Count - 1;
if (openedForms < 2) return;
int startLocation = 0;
int childrenHeight =
(int)((ClientSize.Height / openedForms) * horizontalTileHeightMultiplier);
List<Form> children = MdiChildren.OrderBy(f => f.Name).ToList();
foreach (Form child in children)
{
child.Size = new Size(ClientSize.Width - SystemInformation.VerticalScrollBarWidth - 4, childrenHeight);
child.Location = new Point(0, startLocation);
startLocation += childrenHeight;
}
}

Draw Bézier curve between two related TreeViewItems

I want to draw some Bézier curve inside a canvas on the left side of a treeview to display refrerences between items. I did implement a TreeView with lazy loading and mvvm based on a tutorial from Josh Smith.
This is Minimum example how it should look.
To draw the line I want use a canvas element. So I need the correct coordinate of a TreeViewItem relative to TreeView. I didt get the screenPosition using the Initialized Event.
private static void OnTreeViewItemIsInitialized(object sender, RoutedEventArgs e)
{
// Only react to the Selected event raised by the TreeViewItem
// whose IsSelected property was modified. Ignore all ancestors
// who are merely reporting that a descendant's Selected fired.
if (!Object.ReferenceEquals(sender, e.OriginalSource))
return;
TreeViewItem item = e.OriginalSource as TreeViewItem;
var parent = item.Parent;
var point = item.PointToScreen(new Point(0, 0));
var treeViewItemViewModel = item.DataContext as TreeViewItemViewModel;
if (treeViewItemViewModel != null)
treeViewItemViewModel.Coordinate = point;
}
Two challanges Iam running into using this Code: first i only get the screenposition, second I dont know how to get the parent of TreeViewItem to get calculate the correct position.
Maybe Iam on the wrong way to solve my problem? I did found some example to do something similar, but no example for a spline between two TreeViewItems of the same TreeView.

Container UserControl - Handle and Modify Added Controls

I'm trying to create a custom container as UserControl.
My Goal: I want to be able to drag controls inside the designer and handle incoming controls inside the code of my usercontrol.
Example: I place my container somewhere and then add a button. In this momemt I want my usercontrol to automatically adjust the width and position of this button. Thats the point where Im stuck.
My code:
[Designer("System.Windows.Forms.Design.ParentControlDesigner, System.Design", typeof(IDesigner))]
public partial class ContactList : UserControl
{
public ContactList()
{
InitializeComponent();
}
private void ContactList_ControlAdded(object sender, ControlEventArgs e)
{
e.Control.Width = 200; // Nothing happens
e.Control.Height = 100; // Nothing happens
MessageBox.Show("Test"); // Firing when adding a control
}
}
The MessageBox is working well. The set width and height is ignored.
The question is just "why?".
EDIT
I've just noticed, when placing the button and recompiling with F6 the button gets resized to 200x100. Why isnt this working when placing?
I mean... the FlowLayoutPanel handles added controls right when you place it. Thats the exact behaviour im looking for.
Using OnControlAdded
To fix your code, when you drop a control on container and you want to set some properties in OnControlAdded you should set properties using BeginInvoke, this way the size of control will change but the size handles don't update. Then to update the designer, you should notify the designer about changing size of the control, using IComponentChangeService.OnComponentChanged.
Following code executes only when you add a control to the container. After that, it respects to the size which you set for the control using size grab handles. It's suitable for initialization at design-time.
protected override void OnControlAdded(ControlEventArgs e)
{
base.OnControlAdded(e);
if (this.IsHandleCreated)
{
base.BeginInvoke(new Action(() =>
{
e.Control.Size = new Size(100, 100);
var svc = this.GetService(typeof(IComponentChangeService))
as IComponentChangeService;
if (svc != null)
svc.OnComponentChanged(e.Control,
TypeDescriptor.GetProperties(e.Control)["Size"], null, null);
}));
}
}

WPF- Horizontal and Vertical position of a Run Element in a FlowDocument

Is there anyway to get the horizontal position(pixel) and vertical position(pixel) of a Run element in a FlowDocument?
Edit:
All i need to do is scroll to that position and make it the top line of the FlowDocument.
To Answer Your Question
The code needed to get the position of a content element in a document is all internal to .NET and not publically exposed. You would need access to an IContentHost implementation, which the built-in document viewers do not publically expose. So, there is no supported way to do what you are asking.
To Solve Your Actual Problem
There is a way to achieve your desired result of scrolling the element to the top of the view. What you want to do is scroll to the end of the document, then call BringIntoView on the element you want to have at the top.
There are multiple ways a FlowDocument can be displayed in an application. How you handle the scrolling depends on which control you are using to present the FlowDocument.
In a RichTextBox, use the ScrollToEnd method.
In a FlowDocumentScrollViewer, you will need to get its internal ScrollViewer and call ScrollToBottom on it. (You have to wait until the control is loaded before you can get a template part from it.)
private void MyControl_Loaded(object sender, RoutedEventArgs e)
{
mScrollViewer = mViewer.Template.FindName("PART_ContentHost", mViewer) as ScrollViewer;
}
In a FlowDocumentReader, the process is a bit more complex.
When the control is loaded, register for changes to the ViewingMode property and run the handler once to account for the starting value:
private void MyControl_Loaded(object sender, RoutedEventArgs e)
{
var descriptor = DependencyPropertyDescriptor.FromProperty(FlowDocumentReader.ViewingModeProperty, typeof(FlowDocumentReader));
descriptor.AddValueChanged(mReader, (s, a) => Reader_ViewModeChanged());
Reader_ViewModeChanged();
}
In the handler, dig in to find the ScrollViewer. It will only be present when the ViewingMode is set to Scroll:
private void Reader_ViewModeChanged()
{
mScrollViewer = null;
if (mReader.ViewingMode == FlowDocumentReaderViewingMode.Scroll)
{
var contentHost = mReader.Template.FindName("PART_ContentHost", mReader) as DependencyObject;
if (contentHost != null && VisualTreeHelper.GetChildrenCount(contentHost) > 0)
{
var documentScrollViewer = VisualTreeHelper.GetChild(contentHost, 0) as FlowDocumentScrollViewer;
if (documentScrollViewer != null)
{
documentScrollViewer.ApplyTemplate();
mScrollViewer = documentScrollViewer.Template.FindName("PART_ContentHost", documentScrollViewer) as ScrollViewer;
}
}
}
}
Once you have the ScrollViewer, you can call ScrollToBottom on it when desired.
Now, scroll to the bottom of the document, then call BringIntoView on your Run, and it should be at the top of the view.
Does not bring it to the top but just call BringIntoView on the Run. Save a reference to the Run.
It may be late but i still want to share the way i DID it in WPF.
You need an offset to do so.
As the above said: Flow gave you:
flow.ScrollToHome(); // Bottom
But also gave: ScrollToVerticalOffset (get from Rect)
if you have index (offset of the char/line) - you can find it in you saved data or get the TextPointer with flow.Selection.Start/End
TextPointer t_st = flow.Selection.Start;
double offset = flow.Document.ContentStart.GetOffsetToPosition(t_st);
private void gotoOffset(double offset)
{
TextPointer myTextPointer1 = flow.Document.ContentStart.GetPositionAtOffset((int)offset);
flow.Selection.Select(myTextPointer1, myTextPointer1);
flow.Focus();
Rect screenPos2 = myTextPointer1.GetCharacterRect(LogicalDirection.Forward);
double offset2 = screenPos2.Top;
Thread.Sleep(100);
flow.ScrollToVerticalOffset(offset2);
flow.Focus();
}
As the code above, We get the Rect from TextPointer, the Textpointer and get from Offset.
The focus just to make sure to place the cursor in right place.
Sometime the issue happen when you jump to many offset.
I recomment to trigger flow.ScrollToHome(); Before jump (because this ScrollToVerticalOffset true from the start, not any line)

Switching Panels on button event

I have a row of panels on my WindowsForm which have two buttons on each, Move Up and Move down. I want it so that when the user clicks down it switches the current panel with the panel beneath it essentially taking its place. (swapping them over). I have 10 instances of the same panel so something simple like the below isnt working because all the panels are called DataPanel. Any help is appreciated.
private void MoveDownEnabled_Click(object sender, EventArgs e)
{
Point temp = DataPanel.location;
DataPanel.Location = Panel2.Location;
Panel2.Location = temp;
}
I would recommend storing the Panel in a collection, for a start. This will make the code for accessing the next panel, very clean.
ArrayList panels = new ArrayList();
panels.add(panel);
panels.add(panel2);
panels.add(panel3);
ANd also keep a value, index, to show where in the list you're up to.
private int index = 0; // Set it to a default value.
Then ammend your button click code:
private void MoveDownEnabled_Click(object sender, EventArgs e)
{
Panel Current = panels.get(index);
if(index < panels.Count)
{
// Grab the next item.
Panel Next = panels.get(++ index);
// And this part is up to you!
}
}
You seem to have the right idea with how you're swapping them around, so I think now I've set this up for you, you can work the rest out for yourself.
Don't use your variable name to differentiate controls at runtime, use their properties or their instances. In this case, you can use the Panel's Name property to differentiate, or its Tag property which can hold an object type. Also, I recommend storing the Panels in a collection such as a List so that they can be iterated and accessed easily.
For example (as a general idea)
//won't get nested panels but sounds like you don't need that
var listOfPanels = this.Controls.OfType<Panel>().ToList();
//access the panel you want by name
Panel pnl = listOfPanels.Where(x => x.Name == "panelName").FirstOrDefault();
In your case, your Panel events will be send the panel as the sender in your event handler.
private void MoveDownEnabled_Click(object sender, EventArgs e)
{
Panel selectedPanel = sender as Panel;
if(selectedPanel != null)
{
Point temp = DataPanel.location;
DataPanel.Location = selectedPanel.Location;
selectedPanel.Location = temp;
}
}
I think a simple swap method should do:
public void SwapPanels(System.Windows.Forms.Panel pnl1, System.Windows.Forms.Panel pnl2)
{
var localtion1 = pnl1.Location;
var location2 = pnl2.Location;
pnl2.Location = localtion1;
pnl1.Location = location2;
}
if you want to swap any two panels, just call this method to swap them. if you have many panels, just store them in a list or so and swap them freely.

Categories

Resources