Draw line number of ListBox items in a separated control - c#

I want to draw line numbers in the left of a ListBox, very similar to what AvalonEdit does with LineNumberMargin. When ShowLineNumbers is true, it creates LineNumberMargin like this.
Anyways, I took a look at how they do it, and understood, and now I'm trying to apply something similar but using a ListBox (and its items) as a source for the drawing.
My control works like this: I have a separated ItemsControl docked to the left of ListBox. Each ItemsControl's item is an UIElement. DesignerLineNumberMargin is one of the ItemsControl's item and when the ItemsSource of the ListBox is set, I attach the ListBox to the DesignerLineNumberMargin. When DesignerLineNumberMargin is rendered, I iterate over ListBox's items and draw the line numbers.
DesignerLineNumberMargin.cs
public interface IMetadataAware
{
void Attach(ItemsControl control);
void Detach(ItemsControl control);
}
public class DesignerLineNumberMargin : FrameworkElement, IMetadataAware
{
private ItemsControl control;
private Typeface typeface;
private double emSize;
private int maxLineNumberLength = 2;
static DesignerLineNumberMargin()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(DesignerLineNumberMargin),
new FrameworkPropertyMetadata(typeof(DesignerLineNumberMargin)));
}
protected override Size MeasureOverride(Size availableSize)
{
typeface = CreateTypeface();
emSize = (double)GetValue(TextBlock.FontSizeProperty);
var text = CreateText(new string('9', maxLineNumberLength));
return new Size(text.Width, 0);
}
private FormattedText CreateText(string text)
{
return
new FormattedText(
text,
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
typeface,
emSize,
(Brush)GetValue(Control.ForegroundProperty));
}
protected override void OnRender(DrawingContext drawingContext)
{
if (control == null)
return;
var renderSize = RenderSize;
var foreground = (Brush)GetValue(Control.ForegroundProperty);
for (int index = 0; index < control.Items.Count; index++)
{
var item = control.Items[index];
var container = (FrameworkElement)control.ItemContainerGenerator.ContainerFromItem(item);
var text = CreateText((index + 1).ToString(CultureInfo.CurrentCulture));
//var y = container.Height;
var y = RenderSize.Height / (double)control.Items.Count;
drawingContext.DrawText(text, new Point(renderSize.Width - text.Width, y + index));
}
}
private Typeface CreateTypeface()
{
var element = this;
return new Typeface(
(FontFamily)element.GetValue(TextBlock.FontFamilyProperty),
(FontStyle)element.GetValue(TextBlock.FontStyleProperty),
(FontWeight)element.GetValue(TextBlock.FontWeightProperty),
(FontStretch)element.GetValue(TextBlock.FontStretchProperty));
}
public void Attach(ItemsControl control)
{
this.control = control;
var descriptor = TypeDescriptor.GetProperties(control)["ItemsSource"];
descriptor.AddValueChanged(control, OnItemsSourceChanged);
}
private void OnItemsSourceChanged(object sender, EventArgs e)
{
if (this.control.ItemsSource is INotifyCollectionChanged)
(this.control.ItemsSource as INotifyCollectionChanged).CollectionChanged += CollectionChanged;
}
private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
InvalidateVisual();
}
public void Detach(ItemsControl control)
{
if (this.control == control)
{
var descriptor = TypeDescriptor.GetProperties(control)["ItemsSource"];
descriptor.RemoveValueChanged(control, OnItemsSourceChanged);
if (this.control.ItemsSource is INotifyCollectionChanged)
(this.control.ItemsSource as INotifyCollectionChanged).CollectionChanged -= CollectionChanged;
this.control = null;
}
InvalidateVisual();
}
}
The problem for me is figuring out the y-coordinate. When OnRender is called I do not know the Height of the ListBoxItem: Height, ActualHeight, DesiredSize is always 0.
Any insights?

I guess that's because your MeasureOverride() returns a Size with only a Width, but Height set to zero.
Try change the return statement to:
return new Size(text.Width, text.Height);

Related

WinForms User Control has ComboBox that causes ToolStripDropDown to auto-close

I have a custom WinForms user control that looks like a combobox but instead opens a ToolStripDropDown that contains another custom user control, called NumericFilterPanel, that has a checkbox, a combobox, and a textbox.
The problem is that when the user click-selects an option for the combobox embedded in the dropdown control, it causes the parent dropdown to hide.
I have set ToolStripDropDown.AutoClose = false, which fixes the original problem, but now I am having difficulty detecting all the situations where the dropdown loses focus, such as when the user clicks on the parent form or switches programs. Sometimes the dropdown remains visible and topmost.
Is there a way to either keep AutoClose = true and prevent the embedded combobox from closing the parent dropdown, or is there a way to always detect when the dropdown has lost focus so I can manually close it?
using System;
using System.Drawing;
using System.Windows.Forms;
namespace mviWinControls
{
public partial class NumericRangeDropDown : UserControl
{
private const int ARROW_HEIGHT = 4;
private Brush arrowBrush = new SolidBrush(Color.FromArgb(77, 97, 133));
private ToolStripDropDown _dropdown;
private ToolStripControlHost _host;
private NumericFilterPanel _filter;
public NumericRangeDropDown()
{
InitializeComponent();
_filter = new NumericFilterPanel();
_filter.DropDown = this;
_host = new ToolStripControlHost(_filter);
_host.Margin = Padding.Empty;
_host.Padding = Padding.Empty;
_dropdown = new ToolStripDropDown();
_dropdown.Margin = Padding.Empty;
_dropdown.Padding = Padding.Empty;
_dropdown.AutoClose = false; // Use this because panel has a combobox. https://social.msdn.microsoft.com/Forums/windows/en-US/dd95b982-820e-4807-8a1f-79c74acab3f8/two-problems-toolstripdropdown?forum=winforms
_dropdown.Items.Add(_host);
_dropdown.Leave += new System.EventHandler(this.DropDown_Leave);
this.Leave += new System.EventHandler(this.DropDown_Leave);
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (components != null) components.Dispose();
if (_dropdown != null) _dropdown.Dispose();
}
base.Dispose(disposing);
}
public override string Text
{
get
{
return base.Text;
}
set
{
base.Text = value;
_filter.SetValue(value);
}
}
protected override void OnPaint(PaintEventArgs e)
{
//base.OnPaint(e);
TextBox _txtDraw = new TextBox();
_txtDraw.Width = this.Width;
using (Bitmap bmp = new Bitmap(_txtDraw.Width, _txtDraw.Height))
{
_txtDraw.DrawToBitmap(bmp, new Rectangle(0, 0, _txtDraw.Width, _txtDraw.Height));
e.Graphics.DrawImage(bmp, 0, 0);
}
StringFormat format = new StringFormat();
format.Alignment = StringAlignment.Near;
format.FormatFlags = StringFormatFlags.NoWrap;
format.LineAlignment = StringAlignment.Center;
using (Brush b = new SolidBrush(this.ForeColor))
e.Graphics.DrawString(this.Text, this.Font, b, this.DisplayRectangle, format);
Point[] arrowPoints = new Point[] { new Point(this.Width - ARROW_HEIGHT * 3 - 2, (this.Height - ARROW_HEIGHT) / 2),
new Point(this.Width - ARROW_HEIGHT + 1 - 2, (this.Height - ARROW_HEIGHT) / 2),
new Point(this.Width - ARROW_HEIGHT * 2 - 2, this.Height - (this.Height - ARROW_HEIGHT) / 2) };
e.Graphics.FillPolygon(arrowBrush, arrowPoints );
}
private void DropDown_Leave(object sender, EventArgs e)
{
HideDropDown();
this.Text = _filter.SummaryText();
}
private void NumericRangeDropDown_Click(object sender, EventArgs e)
{
if (_dropdown.Visible)
HideDropDown();
else
ShowDropDown();
}
public void ShowDropDown()
{
_dropdown.Show(this, new Point(0, this.Height), ToolStripDropDownDirection.Default);
_dropdown.BringToFront();
//_dropdown.Focus();
_filter.Select();
_filter.Focus();
}
public void HideDropDown()
{
_dropdown.Close();
this.Invalidate();
}
}
}
Here's a combobox that can automatically disable and enable the AutoClose property on the host control for you.
Source(I modified it for a combobox versus the DatePicker in their example): http://www.queasy.me/programming/questions/13919634/tool+strip+toolstripdropdownbutton+close+and+lose+window+focus
public partial class CComboBox : ComboBox
{
private bool savedAutoClose;
public CComboBox()
{
InitializeComponent();
}
protected override void OnDropDownClosed(EventArgs e)
{
if (this.Parent != null)
{
var dropDownHost = this.Parent.Parent as ToolStripDropDown; // recursive instead?
if (dropDownHost != null)
dropDownHost.AutoClose = savedAutoClose; // restore the parent's AutoClose preference
}
base.OnDropDownClosed(e);
}
protected override void OnDropDown(EventArgs e)
{
if (this.Parent != null)
{
var dropDownHost = this.Parent.Parent as ToolStripDropDown; // recursive instead?
if (dropDownHost != null)
{
savedAutoClose = dropDownHost.AutoClose;
// ensure that our parent doesn't close while the calendar is open
dropDownHost.AutoClose = false;
}
}
base.OnDropDown(e);
}
}
Having taken a good look at the source code, the bug (and it is a bug) lies in the fact that ToolStripManager, which sets up a message filter to catch mouse-clicks outside the active ToolStrip, checks if the click is within the bounds of a child window.
The problem is that it uses activeToolStrip.ClientRectangle to verify this, and does not check child windows of the window that was clicked. In the case of a ComboBox, the drop-down is a separate child window which floats above everything, and can actually be out of the bounds of the main combo window if the drop-down is large.
The relevant line is:
if (!activeToolStrip.ClientRectangle.Contains(pt.x, pt.y)) {
I have found another solution to temporarily disable the automatic close while the dropdown is open.
Ideally, you are supposed to use a ToolStripComboBox within a ToolStrip rather than just a bare ComboBox. However if you would like to just us a bare one, you can add events to call the relevant private methods to suspend and resume the message filter.
static class ToolStripComboBoxFilter
{
private static Action SuspendMenuMode = (Action) typeof(ToolStripManager)
.GetNestedType("ModalMenuFilter", BindingFlags.NonPublic)
.GetMethod(nameof(SuspendMenuMode), BindingFlags.NonPublic | BindingFlags.Static)
.CreateDelegate(typeof(Action));
private static Action ResumeMenuMode = (Action)typeof(ToolStripManager)
.GetNestedType("ModalMenuFilter", BindingFlags.NonPublic)
.GetMethod(nameof(ResumeMenuMode), BindingFlags.NonPublic | BindingFlags.Static)
.CreateDelegate(typeof(Action));
public static void AddToolStripFilterEvents(this ComboBox combo)
{
combo.DropDown += OnDropDown;
combo.DropDownClosed += OnDropDownClosed;
}
private static void OnDropDown(object sender, EventArgs e)
{
SuspendMenuMode();
}
private static void OnDropDownClosed(object sender, EventArgs e)
{
ResumeMenuMode();
}
}
You can use it like this
myComboBox.AddToolStripFilterEvents();

Adding Custom Controls to a panel programatically with margin

Hello I made a custom text box with an associated label.
and I have a custom Form.
when on a form if I drag drop my custom textbox from the toolbox and set it's properties I can see that it works. However what I would like to do is when I'm on my custom control where I have a TableLayoutPanel (with 3 rows)
on row index 1(mid row) I would like to add my custom controls programatically.
when I do this the text label is somewhere else then the textbox.
My Code and the Image to my problem is below:
MyCustomTextBox:
public class MyLbTextBox : TextBox
{
#region CustomProperties
private Label AssociatedLabel = new Label();
private string _myLbText;
public string MyTextLabel
{
get => _myLbText;
set
{
_myLbText = value;
AssociatedLabel.Text = _myLbText ?? _myBindingField;
Size s = TextRenderer.MeasureText(AssociatedLabel.Text, AssociatedLabel.Font);
AssociatedLabel.Location =
new Point(Location.X - s.Width - AssociatedLabel.Padding.Right, Location.Y);
var MyMargin = this.Margin;
MyMargin.Left = 100;
this.Margin = MyMargin;
}
}
#endregion
private string _myBindingField;
public string MyBindingField
{
get { return _myBindingField; }
set
{
_myBindingField = value;
}
}
private MyJoins.MyExpressions _myExpression;
public MyJoins.MyExpressions MyExpression
{
get => _myExpression;
set => _myExpression = value;
}
public MyLbTextBox()
{
_myExpression = MyJoins.MyExpressions.Equals;
ParentChanged += MyLbTextBox_ParentChanged;
LocationChanged += MyLbTextBox_LocationChanged;
Disposed += MyLbTextBox_Disposed;
}
private void MyLbTextBox_Disposed(object sender, EventArgs e)
{
AssociatedLabel.Dispose();
}
private void MyLbTextBox_LocationChanged(object sender, EventArgs e)
{
Size s = TextRenderer.MeasureText(AssociatedLabel.Text, AssociatedLabel.Font);
AssociatedLabel.Location =
new Point(Location.X - s.Width - AssociatedLabel.Padding.Right, Location.Y);
}
private void MyLbTextBox_ParentChanged(object sender, EventArgs e)
{
AutoAddAssociatedLabel();
}
private void AutoAddAssociatedLabel()
{
if (Parent == null) return;
AssociatedLabel.Padding = new Padding(3);
Size s = TextRenderer.MeasureText(AssociatedLabel.Text, AssociatedLabel.Font);
AssociatedLabel.Location =
new Point(Location.X - s.Width - AssociatedLabel.Padding.Right, Location.Y);
Parent.Controls.Add(AssociatedLabel);
}
}
By the way, this is how I add my controls:
after adding my controls through the property grid
this is how I set them on the screen
private void _mySearchFields_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (_mySearchFields == null) return;
foreach (var searchField in _mySearchFields)
{
if (MySearchFieldsPanel.Contains(searchField.MyControl)) continue;
MySearchFieldsPanel.Controls.Add(searchField.MyControl, 1, 0);
}
var myHeight = MySearchFieldsPanel.Controls.Cast<Control>().Sum(variable => variable.Height);
MyBdPanel.RowStyles[1].Height = myHeight + 40;
}
I appreciate any help
This line is a reason of all problems:
Parent.Controls.Add(AssociatedLabel);
This is bad idea if you are creating composite controls (composite = consisting from several real controls). That will cause layout problems you have experienced and more.
Instead consider either:
Utilize UserControl to create composition.
Create custom control (like you do) but without more controls. If you need label - draw it as text in OnPaint while allocating some space: fixed with margin, adjustable with some property or dynamic with measuring text.

Drag and drop a CustomControl working only from it's background

I have a custom control containing a button with a label underneath. I want to make these controls to be draggable over another one to arrange them as I want in a flowlayoutpanel.
Now is working only if I drag the control from it's background (marked with yellow in the picture bellow) to the other control's yellow marked area, but not if i drag from the button or label area..
How can I make it so I can move the custom control no matter from where I grab and drop it on the other control. Basically to be only one control not like a container for the button and label..
This is my code so far:
private void flowLayoutPanel1_DragEnter(object sender, DragEventArgs e)
{
e.Effect = DragDropEffects.Move;
}
private void flowLayoutPanel1_DragDrop(object sender, DragEventArgs e)
{
CustomControl target = sender as CustomControl;
if (target != null)
{
int targetIndex = FindCSTIndex(target);
if (targetIndex != -1)
{
string pictureBoxFormat = typeof(CustomControl).FullName;
if (e.Data.GetDataPresent(pictureBoxFormat))
{
CustomControl source = e.Data.GetData(pictureBoxFormat) as CustomControl;
int sourceIndex = this.FindCSTIndex(source);
if (targetIndex != -1)
this.flowLayoutPanel1.Controls.SetChildIndex(source, targetIndex);
}
}
}
}
private int FindCSTIndex(CustomControl cst_ctr)
{
for (int i = 0; i < this.flowLayoutPanel1.Controls.Count; i++)
{
CustomControl target = this.flowLayoutPanel1.Controls[i] as CustomControl;
if (cst_ctr == target)
return i;
}
return -1;
}
private void OnCstMouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
CustomControl cst = sender as CustomControl;
cst.DoDragDrop(cst, DragDropEffects.Move);
}
}
And the custom control class:
public class CustomControl : Control
{
private Button _button;
private Label _label;
public CustomControl(Button button, Label label)
{
_button = button;
_label = label;
button.Width = 50;
button.Height = 50;
label.Width = 65;
button.BackgroundImageLayout = ImageLayout.Stretch;
Height = button.Height + label.Height;
Width = 68;
// Width = Math.Max(button.Width, label.Width);
Controls.Add(_button);
_button.Location = new Point(0, 0);
Controls.Add(_label);
_label.Location = new Point(0, button.Height);
}
}
Use MouseDown instead of MouseMove to initiate drag-and-drop (MSDN). You can initiate drag-and-drop in the control code itself (assuming what all CustomControls will be drag-and-drop-able), otherwise you may want to create public method to sign childs (exposing childs is bad idea, unless you already use them outside).
public class CustomControl : Control
{
...
public CustomControl(Button button, Label label)
{
...
_button.MouseDown += OnMouseDown;
_label.MouseDown += OnMouseDown;
}
override void OnMouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
(sender as Control).DoDragDrop(this, DragDropEffects.Move);
}
}
Untested, but should give you idea.
Managed to solve it:
private void flowLayoutPanel1_DragDrop(object sender, DragEventArgs e)
{
Control target = new Control();
target.Parent = sender as Control;
if (target != null)
{
int targetIndex = FindCSTIndex(target.Parent);
if (targetIndex != -1)
{
string cst_ctrl = typeof(CustomControl).FullName;
if (e.Data.GetDataPresent(cst_ctrl))
{
Button source = new Button();
source.Parent = e.Data.GetData(cst_ctrl) as CustomControl;
if (targetIndex != -1)
this.flowLayoutPanel1.Controls.SetChildIndex(source.Parent, targetIndex);
}
}
}
}
private int FindCSTIndex(Control cst_ctr)
{
for (int i = 0; i < this.flowLayoutPanel1.Controls.Count; i++)
{
CustomControl target = this.flowLayoutPanel1.Controls[i] as CustomControl;
if (cst_ctr.Parent == target)
return i;
}
return -1;
}
private void OnCstMouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
Control cst = sender as Control;
cst.DoDragDrop(cst.Parent, DragDropEffects.Move);
}
}

Custom Color Dialog in Windows Forms

Is there a way to have the color dialog display on the form without the window border? To make things a bit more clearer, I want it sort of like the one used in the Paint program so that I'm able to change colors of lines drawn on a picturebox without having to open up the dialog again and again:
I'm placing it inside a vertical groupbox, and I want it to be able to fit in it. Can it be done?
You can easily build it yourself. Use a TableLayoutPanel or FlowLayoutPanel and a add a list of small PictureBox with the background color set on the color you want. Then handle the Click event to return/use their background color.
private void Form1_Load(object sender, EventArgs e)
{
pictureBox3.Click += HandleColorPick;
pictureBox4.Click += HandleColorPick;
pictureBox5.Click += HandleColorPick;
pictureBox6.Click += HandleColorPick;
pictureBox7.Click += HandleColorPick;
pictureBox8.Click += HandleColorPick;
pictureBox9.Click += HandleColorPick;
}
private void HandleColorPick(object sender, EventArgs e)
{
var s =(PictureBox) sender;
MessageBox.Show(s.BackColor.ToString());
}
You'll have to create your own palette.
Here's a usercontrol i wrote with comments. I've added a OnSelectionChanged event that will be fired when user changes color.
You can either use the designer to specify colors, events and other properties or you can hard-code them.
public partial class UserControl1 : UserControl
{
// The event raised when a user changes color
[Category("Behavior")]
public event EventHandler OnSelectionChanged;
/*
* Properties
*
*/
private Color[] m_Colors = new Color[] {}; // Colors on the palette
private Color m_SelectedColor; // Stores selected color
private int m_MaxColorsPerLine = 14; // Max colors per line
public Color[] Colors
{
get { return m_Colors; }
set { m_Colors = value; }
}
[Browsable(false)]
public Color SelectedColor
{
get { return m_SelectedColor; }
}
public int MaxColorsPerLine
{
get { return m_MaxColorsPerLine; }
set { m_MaxColorsPerLine = value; }
}
public UserControl1()
{
InitializeComponent();
}
private void UserControl1_Load(object sender, EventArgs e)
{
this.Controls.Clear();
int COLOR_WIDTH = 16;
int COLOR_HEIGHT = 16;
// Border colors
Color NEUTRAL_COLOR = Color.Black;
Color HIGHLIGHTED_COLOR = Color.Orange;
Color SELECTED_COLOR = Color.Red;
// Populate the palette
for(int i = 0; i < m_Colors.Length; ++i)
{
// Get where the current color should be positioned.
int linePos = (int)Math.Floor((double) i / m_MaxColorsPerLine);
int colPos = i % m_MaxColorsPerLine;
int posX = COLOR_WIDTH * colPos;
int posY = COLOR_HEIGHT * linePos;
// Create the panel that will hold the color
Panel pnl = new Panel();
pnl.Width = COLOR_WIDTH;
pnl.Height = COLOR_HEIGHT;
pnl.Location = new Point(posX, posY);
pnl.BackColor = m_Colors[i];
pnl.ForeColor = NEUTRAL_COLOR;
// Change border color when highlighted
pnl.MouseEnter += (s, args) => pnl.ForeColor = HIGHLIGHTED_COLOR;
// Change border color when mouse leaves
pnl.MouseLeave += (s, args) =>
{
if (pnl.Tag != null && (bool)pnl.Tag == true)
{
pnl.ForeColor = SELECTED_COLOR; // restore selected border color on mouse leave if selected
}
else
{
pnl.ForeColor = NEUTRAL_COLOR; // restore normal border color on mouse leave if not selected
}
};
// Change border color when selected
pnl.Click += (s, args) =>
{
if (pnl.Tag == null || (bool)pnl.Tag == false)
{
pnl.ForeColor = SELECTED_COLOR;
pnl.Tag = true; // Use the Tag member to store whether the color is selected
m_SelectedColor = pnl.BackColor;
// Raise the SelectionChanged event if this panel is not already selected
if (OnSelectionChanged != null)
{
OnSelectionChanged(this, EventArgs.Empty);
}
}
// Unselect other colors
foreach (Panel otherColor in this.Controls)
{
if (otherColor == pnl)
continue;
if (pnl.Tag != null && (bool)pnl.Tag == true)
{
otherColor.ForeColor = NEUTRAL_COLOR;
otherColor.Tag = false;
}
}
};
// Draw borders
pnl.Paint += (s, args) =>
{
Rectangle outterRect = args.ClipRectangle;
Rectangle innerRect = new Rectangle(outterRect.X + 1, outterRect.Y + 1, outterRect.Width - 3, outterRect.Height - 3);
// Draw outter rectangle
args.Graphics.DrawRectangle(
new Pen(
new SolidBrush(pnl.ForeColor), 2),
outterRect);
// Draw inner rectangle
args.Graphics.DrawRectangle(
new Pen(
Brushes.White, 1),
innerRect);
};
// Finally, add color panel to the control
this.Controls.Add(pnl);
}
}
}
Why not some 3rd party opensource control?
http://www.codeproject.com/Tips/638824/Yet-Another-Color-Picker-for-Csharp-VB-NET-WinForm
or
http://www.blackbeltcoder.com/Articles/controls/colorpicker-controls-for-winforms

Silverlight: reposition child window to center

The content inside the child window changes, which causes my child window to lose it's center alignment.... After the content has changed is there anyway to reposition the child window to center... I tried the following and it did not work:
this.horizontalalignment = horizontalalignment.center;
this.verticalalignment = verticalalignment.center;
Thanks
The presence of a RenderTransform on the ChildWindow template seems to be to blame. The TransformGroup is part of the default template to allow you to move around the window.
Here is a hack to reset the transform after you change the layout:
//after you do some change to the childwindow layout
sp.Children.Add(new Button() { Content = "a" });
Dispatcher.BeginInvoke(() =>
{
//reset the transform to zero
(this.GetTemplateChild("ContentRoot") as Grid).RenderTransform = new TransformGroup()
{
Children = { new ScaleTransform(), new SkewTransform(), new RotateTransform(), new TranslateTransform() }
};
});
or more automatically:
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
var contentRoot = this.GetTemplateChild("ContentRoot") as FrameworkElement;
contentRoot.LayoutUpdated += contentRoot_LayoutUpdated;
}
void contentRoot_LayoutUpdated(object sender, EventArgs e)
{
var contentRoot = this.GetTemplateChild("ContentRoot") as FrameworkElement;
var tg = contentRoot.RenderTransform as TransformGroup;
var tts = tg.Children.OfType<TranslateTransform>();
foreach (var t in tts)
{
t.X = 0; t.Y = 0;
}
}
LayoutUpdated gets called often, so you may want to check if contentRoot.ActualWidth and ActualHeight changed to see if you really need to wipe out the transform.
Code
public partial class DialogOptions : ChildWindow
{
public DialogOptions()
{
InitializeComponent();
Loaded += (sender, args) =>
{
VerticalAlignment = VerticalAlignment.Top;
this.SetWindowPosition(new Point(0, 200));
};
}
}
And extention:
public static void SetWindowPosition(this ChildWindow childWindow, Point point)
{
var root = VisualTreeHelper.GetChild(childWindow, 0) as FrameworkElement;
if (root == null)
{
return;
}
var contentRoot = root.FindName("ContentRoot") as FrameworkElement;
if (contentRoot == null)
{
return;
}
var group = contentRoot.RenderTransform as TransformGroup;
if (group == null)
{
return;
}
TranslateTransform translateTransform = null;
foreach (var transform in group.Children.OfType<TranslateTransform>())
{
translateTransform = transform;
}
if (translateTransform == null)
{
return;
}
translateTransform.X = point.X;
translateTransform.Y = point.Y;
}

Categories

Resources