How to set Z-order of a Control using WinForms - c#

I'm writing a custom TextBox that upon gaining focus changes its border style.
As adding a border causes the control to overlap with those neighbouring it, I temporarily bring the text box to the front of the dialog (using textBox.BringToFront()).
However, once editing is complete and focus is lost, I would like to send the control back to its original position in the Z-order.
Is this possible (preferably in a simple way!)

Call the GetChildIndex and SetChildIndex methods of the parent's Controls collection.

There is no Z-order as there was in VB, but you can use the GetChildIndex and SetChildIndex methods to get and set their indexes manually.
Here there's an example of how to use it. You will probably need to keep a record of each controls index though so you can set it back to it when it's finished with.
Something like this is probably what you're after:
// Get the controls index
int zIndex = parentControl.Controls.GetChildIndex(textBox);
// Bring it to the front
textBox.BringToFront();
// Do something...
// Then send it back again
parentControl.Controls.SetChildIndex(textBox, zIndex);

When used with the FlowLayoutPanel this will move a control up or down
/// <summary>
/// When used with the FlowLayoutPanel this will move a control up or down
/// </summary>
/// <param name="sender"></param>
/// <param name="UpDown"></param>
private void C_On_Move(object sender, int UpDown)
{
//If UpDown = 1 Move UP, If UpDown = 0 Move DOWN
Control c = (Control)sender;
// Get the controls index
int zIndex = _flowLayoutPanel1.Controls.GetChildIndex(c);
if (UpDown==1 && zIndex > 0)
{
// Move up one
_flowLayoutPanel1.Controls.SetChildIndex(c, zIndex - 1);
}
if (UpDown == 0 && zIndex < _flowLayoutPanel1.Controls.Count-1)
{
// Move down one
_flowLayoutPanel1.Controls.SetChildIndex(c, zIndex + 1);
}
}

In C Sharp
Control.SetValue(Panel.ZIndexProperty,0);
Control is your control.
0 is index of ZIndex.
0 is default value.

Related

Scrolling to the bottom of page with invisible scrollbars [duplicate]

I have a panel1 with AutoScroll = true.I have to make panel1 scroll with btnUp and btnDown. So far I've made what I was asked for
private void btnUpClicked(Object sender, EventArgs e)
{
if (panel1.VerticalScroll.Value - 55 > 0)
panel1.VerticalScroll.Value -= 55;
else panel1.VerticalScroll.Value = 0;
}
private void btnDownClicked(Object sender, EventArgs e)
{
panel1.VerticalScroll.Value += 55;
}
But now I need to hide Scrollbar or make it invisible. I tried
panel1.VerticalScroll.Visible = false;
but it doesn't work. Any ideas guys?
Ok, I've done the working example of this for you. All you have to do is to change the max value depending on the total size of all the items inside your panel.
Form code:
public partial class Form1 : Form
{
private int location = 0;
public Form1()
{
InitializeComponent();
// Set position on top of your panel
pnlPanel.AutoScrollPosition = new Point(0, 0);
// Set maximum position of your panel beyond the point your panel items reach.
// You'll have to change this size depending on the total size of items for your case.
pnlPanel.VerticalScroll.Maximum = 280;
}
private void btnUp_Click(object sender, EventArgs e)
{
if (location - 20 > 0)
{
location -= 20;
pnlPanel.VerticalScroll.Value = location;
}
else
{
// If scroll position is below 0 set the position to 0 (MIN)
location = 0;
pnlPanel.AutoScrollPosition = new Point(0, location);
}
}
private void btnDown_Click(object sender, EventArgs e)
{
if (location + 20 < pnlPanel.VerticalScroll.Maximum)
{
location += 20;
pnlPanel.VerticalScroll.Value = location;
}
else
{
// If scroll position is above 280 set the position to 280 (MAX)
location = pnlPanel.VerticalScroll.Maximum;
pnlPanel.AutoScrollPosition = new Point(0, location);
}
}
}
Picture example:
You have to set AutoScroll option to False on your panel. I hope you understand what I've done and will get your panel running the way you want. Feel free to ask if you have any questions.
The Panel control takes on the duty you gave it by setting AutoScroll to true pretty serious. This always includes displaying the scrollbar gadget if it is necessary. So what you tried cannot work, hiding the vertical scrollbar forces Panel to recalculate layout since doing so altered the client area. It will of course discover that the scrollbar is required and promptly make it visible again.
The code that does this, Panel inherits it from ScrollableControl, is internal and cannot be overridden. This was intentional.
So using AutoScroll isn't going to get you anywhere. As an alternative, do keep in mind what you really want to accomplish. You simply want to move controls up and down. Easy to do, just change their Location property. That in turn is easiest to do if you put the controls on another panel, big enough to contain them. Set its AutoSize property to True. And implement you buttons' Click event handlers by simply changing that panel's Location property:
private const int ScrollIncrement = 10;
private void ScrollUpButton_Click(object sender, EventArgs e) {
int limit = 0;
panel2.Location = new Point(0,
Math.Min(limit, panel2.Location.Y + ScrollIncrement));
}
private void ScrollDownButton_Click(object sender, EventArgs e) {
int limit = panel1.ClientSize.Height - panel2.Height;
panel2.Location = new Point(0,
Math.Max(limit, panel2.Location.Y - ScrollIncrement));
}
Where panel1 is the outer panel and panel2 is the inner one that contains the controls. Be careful when you use the designer to put controls on it, it has a knack for giving them the wrong Parent. Be sure to use the View + Other Windows + Document Layout helper window so you can see this going wrong. After you filled it, set its AutoSizeMode property to GrowAndShrink so it snaps to its minimum size.
Try this:
panel.AutoScroll = true;
panel.VerticalScroll.Enabled = false;
panel.VerticalScroll.Visible = false;
Edit:
Actually when AutoScroll = true; It will take care of hscroll and vscroll automatically and you wont be able to change it. I found this on Panel.AutoScroll Property on MSDN
AutoScroll maintains the visibility of the scrollbars automatically. Therefore, setting the HScroll or VScroll property to true has no effect when AutoScroll is enabled.
You may try this to workaround this problem, I have copied it from this Link.
Behavior Observations 1:
If AutoScroll is set to true, you can't modify anything in VerticalScroll or HorizontalScroll. AutoScroll means AutoScroll; the control decides when scrollbars are visible, what the min/max is, etc. and you can't change a thing.
So if you want to customize the scrolling (e.g. hide scrollbars), you must set AutoScroll to false.
Looking at the source code for the ScrollableControl with Lutz Roeder's .NET Reflecter, you can see that if AutoScroll is set to true, it ignores your attempts to change property values within the VerticalScroll or HorizontalScroll properties such as MinValue, MaxValue, Visible etc.
Behavior Observations 2:
With AutoScroll set to false, you can change VerticalScroll.Minimum, VerticalScroll.Maximum, VerticalScroll.Visible values.
However, you cannot change VerticalScroll.Value!!! Wtf! If you set it to a non-zero value, it resets itself to zero.
Instead, you must set AutoScrollPosition = new Point( 0, desired_vertical_scroll_value );
And finally, SURPRISE, when you assign positive values, it flips them to negative values, so if you check AutoScrollPosition.X, it will be negative! Assign it positive, it comes back negative.
So yeah, if you want custom scrolling, set AutoScroll to false. Then set the VerticalScroll and HorizontalScroll properties (except Value). Then to change the scroll value, you need to set AutoScrollPosition, even though you aren't using auto scrolling! Finally, when you set the AutoScrollPosition, it will take on the opposite (i.e. negative) value that you assign to it, so if you want to retrieve the current AutoScrollPosition later, for example if you want to offset the scroll value by dragging the mouse to pan, then you need to remember to negate the value returned by AutoScrollPosition before reassigning it to AutoScrollPosition with some offset. WOW. Wtf.
One other thing, if you are trying to pan with the mouse, use the values of Cursor.Position rather than any mouse locations returned by the mouse events parameters. Scrolling the control will cause the event parameter values to be offset as well, which will cause it to start firing mouse move events complete with undesired values. Just use Cursor.Position, because it will use mouse screen coordinates as a fixed frame of reference, which is what you want when you're trying to pan/offset the scroll value.

UWP ListView scroll bar not selectable in touch mode

I have a simple ListView in a Universal Windows App. It scrolls just fine both on a touch-enabled device and on my local machine. On the touch device I would like to be able to tap and drag the scroll bar thumb in order to quickly scroll through the list (same as you would get clicking and dragging the scroll thumb with a mouse). But, when I try to select the scroll bar thumb it does not work; the thumb is not selectable.
At first I thought the scroll thumb was just too small, so I fiddled with the default style of ScrollBar to increase the width. Then I tried tweaking other values in the default ScrollViewer style, like the ScrollingIndicatorMode and ScrollingIndicatorStates so that they would always use MouseIndicator, and making sure all IsHitTestVisible are True. To no avail.
It seems like there must be something in the default styles that allows this but I can't find it through trial and error, and no where in the MSDN docs appears to address this styling.
Is this doable in touch mode?
Is this doable in touch mode?
AFAIK, this is not possible currently. The scroll event of ScrollBar can only be triggered by scrolling the content or moving the Thumb by mouse input.
This is by design, but on the touch device if we want to quickly scroll through the list, we just quickly swipe one item for a short distance, the ListView will firstly speed up the scrolling and then slow down and finally stop. Other controls which contain a ScrollViewer behave the same, in the viewport(content of ScrollViewer) when in touch device, moving the thumb is not work, to speed up the scrolling, you can only give quick swipe gesture on the viewport.
Our suggestion is that you may submit a request to add this new features for developing through the Windows Feedback tool.
For posterity's sake I want to post my work around which actually turned out not being quite as difficult as I thought and looks really good now.
The basic idea is to use a Slider control (has a Thumb that is select-able, drag-able, and moves along a track) with a vertical orientation and to sync the Slider's Value property (which changes when dragged) to the ListView's ScrollIntoView method so you can scroll the ListView as the Slider Thumb is dragged. Vice-versa (move the Slider Thumb when the ListView scrolls normally) is also required for a clean, seamless experience. I have some code sample below to get you started (not everything is there to work right away). In addition, edit the Slider's default Style template to make it look exactly like a scroll bar (and even even add a tooltip to display the current value when dragging).
Note: I'm using WinRTXamlToolkit in code-behind to traverse the VisualTree easily.
Code behind:
public sealed partial class MainPage : Page
{
private bool _isScroll = false;
private bool _isSlide = false;
public MainPage()
{
this.InitializeComponent();
var vm = new ViewModel();
vm.ValueChanged += LetterSliderValueChanged;
DataContext = vm;
}
/// <summary>
/// Bring list items into view on the screen based on the value (letter) of the slider
/// </summary>
/// <param name="sender">The view model to bring into view</param>
private void LetterSliderValueChanged(object sender, RoutedEventArgs e)
{
if (_isScroll) return;
if (sender == null) return;
_isSlide = true;
ListView?.ScrollIntoView(sender, ScrollIntoViewAlignment.Leading);
}
/// <summary>
/// Update the position of the slider when the ListView is scrolling from a normal touch
/// </summary>
private void ScrollViewerViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
{
if (_isSlide)
{
_isSlide = false;
return;
}
_isScroll = true;
var scrollViewer = sender as ScrollViewer;
var scrollBars = scrollViewer.GetDescendantsOfType<ScrollBar>();
var verticalBar = scrollBars.FirstOrDefault(x => x.Orientation == Orientation.Vertical);
// Normalize the scales to move the slider thumb in sync with scrolling
var sliderTotal = LetterSlider.Maximum - LetterSlider.Minimum;
var barTotal = verticalBar.Maximum - verticalBar.Minimum;
var barPercent = verticalBar.Value / barTotal;
LetterSlider.Value = (barPercent * sliderTotal) + LetterSlider.Minimum;
_isScroll = false;
}
/// <summary>
/// Add the slider method to the ListView's ScrollViewer Viewchanged event
/// </summary>
private void ListViewLoaded(object sender, RoutedEventArgs e)
{
var listview = sender as ListView;
if (listview == null) return;
var scrollViewer = listview.GetFirstDescendantOfType<ScrollViewer>();
scrollViewer.ViewChanged -= ScrollViewerViewChanged;
scrollViewer.ViewChanged += ScrollViewerViewChanged;
}
}
XAML:
<Slider x:Name="LetterSlider" Orientation="Vertical"
Value="{Binding SliderValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Maximum="{Binding MaxSliderLetter, Mode=OneTime}"
Minimum="{Binding MinSliderLetter, Mode=OneTime}"
RenderTransformOrigin="0.5,0.5">
<Slider.RenderTransform>
<RotateTransform Angle="180"/>
</Slider.RenderTransform>
</Slider>
View Model:
//Method to update the ListView to show items based on the letter of the slider
public RoutedEventHandler ValueChanged;
public int SliderValue
{
get { return _sliderValue; }
set
{
_sliderValue = value;
NotifyPropertyChanged("SliderValue");
char letter = (char)_sliderValue;
var items = ItemsGroup.FirstOrDefault(i => (char)i.Key == letter);
if (items == null) return;
ValueChanged(items.FirstOrDefault(), new RoutedEventArgs());
}
}

WPF, ScrollViewer consuming touch before longpress

I'm kinda new to this WPF world.
And I'm kinda confused on why my scrollview is consuming my touch event.
My current situation is this:
I have this ScrollViewer with an ItemsControl. In this ItemsControl I'm using a Wrappanel to show a number of Rectangles. My ScrollViewer should make it possible to scroll vertically to show the Rectangles that are wrapped downward.
On each of these Rectangles I made a CustomBehaviour with all kinds of handlers.
One of these handlers is a 'creatively' made way to handle LongPressGestures.
The problem is as follows, before my longpress is detected by the behaviour, my ScrollViewer is capturing my TouchDevice in its PreviewTouchMove handler.
How can I prevent my ScrollViewer from capturing my TouchDevice too early?
How can I make sure that I can scroll my ScrollViewer and do my LongPress, DoubleClick (which still works), SingleClick (which still works as well) and other gestures I might add to this custom behaviour?
I've found similar questions on stackoverflow and google that I just not figure out for my specific case.
Something with a CustomThumb <-- This link solves the problem by making a CustomThumb. Can I somehow re-use this for my behaviour? By capturing the TouchDevice early in my behaviour handlers?
If all things fail. Is there an alternative to this ScrollViewer & CustomBehaviour combination?
EDIT:
In the meantime. I retried the CustomThumb-method. I got the longpress to work now from my CustomBehaviour while the UIElements (with that behaviour) are located on a ScrollViewer.
However the scroll functionality of the ScrollViewer still does not work.
The bounty I added will also be awarded to the person that helps me get that to work properly again (since the answer should lie in the same direction as this CustomThumb solution).
I had a similar issue before. I could not use scrolling, touch and style in the same time. At the end I developped a custom ScrollViewer. It is easier than you think, since you only need to watch / change some basic methods.
In your case you can check whether the user pressed on an empy surface or a list item. If it is a list item, you need to check whether it was a short press (so, touchup also occured after touchdown) or a long one.
Scrolling can be configured with PanningMode. It allows you to scroll with finger all over the usercontrol.
Here is my version of scrollviewer. It turns scrolling mode off when user pressed a button and turned on afterwards.
public class ScrollViewerWithTouch : ScrollViewer
{
/// <summary>
/// Original panning mode.
/// </summary>
private PanningMode panningMode;
/// <summary>
/// Set panning mode only once.
/// </summary>
private bool panningModeSet;
/// <summary>
/// Initializes static members of the <see cref="ScrollViewerWithTouch"/> class.
/// </summary>
static ScrollViewerWithTouch()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ScrollViewerWithTouch), new FrameworkPropertyMetadata(typeof(ScrollViewerWithTouch)));
}
protected override void OnManipulationCompleted(ManipulationCompletedEventArgs e)
{
base.OnManipulationCompleted(e);
// set it back
this.PanningMode = this.panningMode;
}
protected override void OnManipulationStarted(ManipulationStartedEventArgs e)
{
// figure out what has the user touched
var result = VisualTreeHelper.HitTest(this, e.ManipulationOrigin);
if (result != null && result.VisualHit != null)
{
var hasButtonParent = this.HasButtonParent(result.VisualHit);
// if user touched a button then turn off panning mode, let style bubble down, in other case let it scroll
this.PanningMode = hasButtonParent ? PanningMode.None : this.panningMode;
}
base.OnManipulationStarted(e);
}
protected override void OnTouchDown(TouchEventArgs e)
{
// store panning mode or set it back to it's original state. OnManipulationCompleted does not do it every time, so we need to set it once more.
if (this.panningModeSet == false)
{
this.panningMode = this.PanningMode;
this.panningModeSet = true;
}
else
{
this.PanningMode = this.panningMode;
}
base.OnTouchDown(e);
}
private bool HasButtonParent(DependencyObject obj)
{
var parent = VisualTreeHelper.GetParent(obj);
if ((parent != null) && (parent is ButtonBase) == false)
{
return HasButtonParent(parent);
}
return parent != null;
}
}

Controlling which items leave a stackpanel's visible area

Is there a way to control which items leave the visible area of a stackpanel when resizing?
Thanks
UPDATE 1
I have a fixed number of buttons inside a stackpanel. When resizing the visible area of the stackpanel, each button automatically hides or shows depending on the available space. What I'd like to achieve, through some event, control which of button hides or shows when stackpanel resizing occurs.
The reason is, I'd like to create a minimized version of the button instead of hiding the button.
In the past I have done something like this by altering the ContentTemplate of an object based on the object's size.
Typically I add an event to both the Loaded and SizeChanged events of the parent object, and from there figure out if the control is visible or not. If not, I change the template to a smaller version of the template.
In reference to your comment here about the SizeChanged event not firing, that is probably because you have your objects in a StackPanel, which will grow/shrink to fit the size of it's children, not to match the size of it's parent (the Grid cell).
You can probably also do this using a DataTrigger and Converter on the actual UI object, so it automatically checks to see if the Template should changed when the control's ActualWidth or ActualHeight changes.
I have a helper class I use to determine the exact visibility of a UI control within it's parent object, to find out if it's fully or partially visible, or hidden entirely. The code can be found in this answer, although I'll also copy it here:
public enum ControlVisibility
{
Hidden,
Partial,
Full,
FullHeightPartialWidth,
FullWidthPartialHeight
}
/// <summary>
/// Checks to see if an object is rendered visible within a parent container
/// </summary>
/// <param name="child">UI element of child object</param>
/// <param name="parent">UI Element of parent object</param>
/// <returns>ControlVisibility Enum</returns>
public static ControlVisibility IsObjectVisibleInContainer(
FrameworkElement child, UIElement parent)
{
GeneralTransform childTransform = child.TransformToAncestor(parent);
Rect childSize = childTransform.TransformBounds(
new Rect(new Point(0, 0), new Point(child.Width, child.Height)));
Rect result = Rect.Intersect(
new Rect(new Point(0, 0), parent.RenderSize), childSize);
if (result == Rect.Empty)
{
return ControlVisibility.Hidden;
}
if (result.Height == childSize.Height && result.Width == childSize.Width)
{
return ControlVisibility.Full;
}
if (result.Height == childSize.Height)
{
return ControlVisibility.FullHeightPartialWidth;
}
if (result.Width == childSize.Width)
{
return ControlVisibility.FullWidthPartialHeight;
}
return ControlVisibility.Partial;
}
You can get a control's visibility like this:
ControlVisibility ctrlVisibility =
WPFHelpers.IsObjectVisibleInContainer(button, parent);
if (ctrlVisibility == ControlVisibility.Full
|| isVisible == ControlVisibility.FullWidthPartialHeight)
{
// Set big template
}
else
{
// Set little template
}
The reason why the "SizeChanged" event wasn't firing was that I had set it's height to a fixed value. After setting it to "auto" the event fired when resizing it's parent container.

How to check if combobox drop down list is revealed up or down?

I have control (implemented C#, .Net 2.0) that inherits from combobox. It has filtering and other stuff. To keep UI right, when amount of items during filtering falls, drop down list changes its size to fit the number of items left (it is done by NativeMethods.SetWindowPos(...)).
Is there any way to check if drop down list is revealed up or down (literally) - not to check if it is open, it is open, but in which direction, upwards or downwards?
cheers, jbk
The ComboBox has two events (DropDown and DropDownClosed) that are fired when the dropdown part opens and closes, so you might want to attach handlers to them to monitor the state of the control.
Alternatively, there's also a boolean property (DroppedDown) which should tell you the current state.
ComboBoxes open downwards or upwards depending on the space they have to open: if they have avaliable space below them they'll open downwards as usual, if not they'll open upwards.
So you simply have to check if they have space enough below them to know. Try this code:
void CmbTestDropDown(object sender, EventArgs e)
{
Point p = this.PointToScreen(cmbTest.Location);
int locationControl = p.Y; // location on the Y axis
int screenHeight = Screen.GetBounds(new Point(0,0)).Bottom; // lowest point
if ((screenHeight - locationControl) < cmbTest.DropDownHeight)
MessageBox.Show("it'll open upwards");
else MessageBox.Show("it'll open downwards");
}
So i found an answer:
Here we have both handles to combobox:
/// <summary>
/// Gets a handle to the combobox
/// </summary>
private IntPtr HwndCombo
{
get
{
COMBOBOXINFO pcbi = new COMBOBOXINFO();
pcbi.cbSize = System.Runtime.InteropServices.Marshal.SizeOf(pcbi);
NativeMethods.GetComboBoxInfo(this.Handle, ref pcbi);
return pcbi.hwndCombo;
}
}
And to dropdownlist of combobox:
/// <summary>
/// Gets a handle to the combo's drop-down list
/// </summary>
private IntPtr HwndDropDown
{
get
{
COMBOBOXINFO pcbi = new COMBOBOXINFO();
pcbi.cbSize = System.Runtime.InteropServices.Marshal.SizeOf(pcbi);
NativeMethods.GetComboBoxInfo(this.Handle, ref pcbi);
return pcbi.hwndList;
}
}
Now, we can get rectangles from handles:
RECT comboBoxRectangle;
NativeMethods.GetWindowRect((IntPtr)this.HwndCombo, out comboBoxRectangle);
and
// get coordinates of combo's drop down list
RECT dropDownListRectangle;
NativeMethods.GetWindowRect((IntPtr)this.HwndDropDown, out dropDownListRectangle);
now we can check:
if (comboBoxRectangle.Top > dropDownListRectangle.Top)
{
....
}

Categories