Combobox Dropdown Position

Windows Forms ComboBox DropDown Position

Here is an extended ComboBox which have 2 new useful features to let you set the position and size of drop-down:

  • DropDownAlignment: You can set it to Left, then the drop-down will appear in it's normal position and it's left is aligned with left of control. If you set it to Middle, the middle of drop-down will be aligned with control and if you set it to Right, the right of drop-down will be aligned with right of control.

  • AutoWidthDropDown: If you set it to true to, then DropdownWidth will be set to the width of the longest item. If you set it to false it uses Width as DropDownWidth.

Here is appearance of drop-down after setting AutoWidthDropDown to true and DropDownAlignment to Left, Middle and Right:

Left

Middle

Right

Implementation - ComboBox with DropDown Position and AutoWith DropDown

You can handle WM_CTLCOLORLISTBOX message, the lparam is the handle of drop-down and then you can set position of drop-down using SetWindowPos.

Also you can calculate width of longest item and set as DropDownWidth if AutoWidthDropDown is true.

using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Linq;
public class MyComboBox : ComboBox
{
private const UInt32 WM_CTLCOLORLISTBOX = 0x0134;
private const int SWP_NOSIZE = 0x1;
[DllImport("user32.dll")]
static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter,
int X, int Y, int cx, int cy, uint uFlags);
public enum DropDownAlignments { Left = 0, Middle, Right }
public bool AutoWidthDropDown { get; set; }
public DropDownAlignments DropDownAlignment { get; set; }
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_CTLCOLORLISTBOX) {
var bottomLeft = this.PointToScreen(new Point(0, Height));
var x = bottomLeft.X;
if (DropDownAlignment == MyComboBox.DropDownAlignments.Middle)
x -= (DropDownWidth - Width) / 2;
else if (DropDownAlignment == DropDownAlignments.Right)
x -= (DropDownWidth - Width);
var y = bottomLeft.Y;
SetWindowPos(m.LParam, IntPtr.Zero, x, y, 0, 0, SWP_NOSIZE);
}
base.WndProc(ref m);
}
protected override void OnDropDown(EventArgs e)
{
if (AutoWidthDropDown)
DropDownWidth = Items.Cast<Object>().Select(x => GetItemText(x))
.Max(x => TextRenderer.MeasureText(x, Font,
Size.Empty, TextFormatFlags.Default).Width);
else
DropDownWidth = this.Width;
base.OnDropDown(e);
}
}

ComboBox Dropdown Position

Tricky problem. I could not find a good fix for this, merely a workaround. Add a new class and paste the code shown below. Compile. Drop the new control from the top of the toolbox onto your form.

The workaround is not a very good one. The problem is that the dropdown window will ignore the attempt to move it until the drop-down animation is complete. The visual effect is not great, you'll see the window drop down, then jump to the left. I don't know an other way to slap it over the head, maybe somebody else does.

C# version:

using System;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;

public class MyComboBox : ComboBox {
protected override void OnDropDown(EventArgs e) {
// Is dropdown off the right side of the screen?
Point pos = this.PointToScreen(this.Location);
Screen scr = Screen.FromPoint(pos);
if (scr.WorkingArea.Right < pos.X + this.DropDownWidth) {
this.BeginInvoke(new Action(() => {
// Retrieve handle to dropdown list
COMBOBOXINFO info = new COMBOBOXINFO();
info.cbSize = Marshal.SizeOf(info);
SendMessageCb(this.Handle, 0x164, IntPtr.Zero, out info);
// Move the dropdown window
RECT rc;
GetWindowRect(info.hwndList, out rc);
int x = scr.WorkingArea.Right - (rc.Right - rc.Left);
SetWindowPos(info.hwndList, IntPtr.Zero, x, rc.Top, 0, 0, 5);
}));
}
base.OnDropDown(e);
}

// P/Invoke declarations
private struct COMBOBOXINFO {
public Int32 cbSize;
public RECT rcItem, rcButton;
public int buttonState;
public IntPtr hwndCombo, hwndEdit, hwndList;
}
private struct RECT {
public int Left, Top, Right, Bottom;
}
[DllImport("user32.dll", EntryPoint = "SendMessageW", CharSet = CharSet.Unicode)]
private static extern IntPtr SendMessageCb(IntPtr hWnd, int msg, IntPtr wp, out COMBOBOXINFO lp);
[DllImport("user32.dll")]
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr after, int x, int y, int cx, int cy, int flags);
[DllImport("user32.dll")]
private static extern bool GetWindowRect(IntPtr hWnd, out RECT rc);
}

VB.Net version:

Imports System
Imports System.Drawing
Imports System.Windows.Forms
Imports System.Runtime.InteropServices

Public Class MyComboBox
Inherits ComboBox

'P/Invoke declarations
Private Structure COMBOBOXINFO
Public cbSize As Int32
Public rcItem As RECT
Public rcButton As RECT
Public buttonState As Integer
Public hwndCombo As IntPtr
Public hwndEdit As IntPtr
Public hwndList As IntPtr
End Structure

Private Structure RECT
Public Left As Integer
Public Top As Integer
Public Right As Integer
Public Bottom As Integer
End Structure

<DllImport("user32.dll", EntryPoint:="SendMessageW", CharSet:=CharSet.Unicode)>
Private Shared Function SendMessageCb(hWnd As IntPtr, msg As Integer, wp As IntPtr, ByRef lp As COMBOBOXINFO) As IntPtr
End Function

<DllImport("user32.dll")>
Private Shared Function SetWindowPos(hWnd As IntPtr, after As IntPtr, x As Integer, y As Integer, cx As Integer, cy As Integer, flags As Integer) As Boolean
End Function

<DllImport("user32.dll")>
Private Shared Function GetWindowRect(hWnd As IntPtr, ByRef rc As RECT) As Boolean
End Function

Protected Overrides Sub OnDropDown(e As EventArgs)
' Is dropdown off the right side of the screen?
Dim pos As Point = Me.PointToScreen(Me.Location)
Dim scr As Screen = Screen.FromPoint(pos)

If (scr.WorkingArea.Right < pos.X + Me.DropDownWidth) Then
Me.BeginInvoke(New Action(Sub()

'Retrieve handle to dropdown list
Dim info As COMBOBOXINFO = New COMBOBOXINFO()
info.cbSize = Marshal.SizeOf(info)
SendMessageCb(Me.Handle, &H164, IntPtr.Zero, info)
'Move the dropdown window
Dim rc As RECT
GetWindowRect(info.hwndList, rc)
Dim x As Integer = scr.WorkingArea.Right - (rc.Right - rc.Left)
SetWindowPos(info.hwndList, IntPtr.Zero, x, rc.Top, 0, 0, 5)
End Sub))
End If

MyBase.OnDropDown(e)

End Sub

End Class

changing combo box drop down position in winforms

You can achieve this by this post
ComboBox Dropdown Position

This works fine when I use below codes

MyComboBox cmb = new MyComboBox();
cmb.Dock = DockStyle.Right;
cmb.Items.Add("Hello world");
cmb.Items.Add("How are you man");
cmb.SelectedIndex = 0;
this.Controls.Add(cmb);

C# Custom ComboBox - DropDown Position

You can use the ToolStripDropDown.Show Method (Control, Point, ToolStripDropDownDirection) overload to control the drop direction. The code will need to perform bounds checking to decide whether to place the dropdown above or below the textbox.

The following is a simplistic method for doing the bounds checking and was only tested on a single screen configuration.

First, make textbox a class level variable.

private TextBox textbox;
public CustomComboBox()
{
//var textbox = new TextBox();
textbox = new TextBox();

The display logic is as follows.

void Button_Click(object sender, EventArgs e)
{
Point textBoxScreenLocation = textbox.PointToScreen(textbox.Location);

// try to position _dropDown below textbox
Point pt = textBoxScreenLocation;
pt.Offset(0, textbox.Height);

// determine if it will fit on the screen below the textbox
Size dropdownSize = _dropDown.GetPreferredSize(Size.Empty);
Rectangle dropdownBounds = new Rectangle(pt, dropdownSize);

if (dropdownBounds.Bottom <= Screen.GetWorkingArea(dropdownBounds).Bottom)
{ // show below
_dropDown.Show(pt, ToolStripDropDownDirection.BelowRight);
}
else
{ // show above
_dropDown.Show(textBoxScreenLocation, ToolStripDropDownDirection.AboveRight);
}
}
}

WPF ComboBox DropDown Placement

I've done something similar before - I ended up deriving from ComboBox, getting the popup part of the control and using the CustomPopupPlacementCallback to position it. Something like this...

class MyComboBox : ComboBox
{
public override void OnApplyTemplate()
{
base.OnApplyTemplate();

var popup = (Popup)Template.FindName("PART_Popup", this);
popup.Placement = PlacementMode.Custom;
popup.CustomPopupPlacementCallback = placePopup;
}

private CustomPopupPlacement[] placePopup(Size popupSize, Size targetSize, Point offset)
{
var placements = new[] { new CustomPopupPlacement() };
placements[0].Point = // position the drop-down here!
return placements;
}
}

Positon of DropDown list of ComboBox in UWP

The DropDown of a ComboBox is actually a Popup, and the position where to show this Popup is defined in the code behind, and we can't access to it. One workaround is finding this Popup and relocate it when it is opened, but using this method we need to calculate the VerticalOffset property each time when it is opened, and there are quite many scenarios for different value of VerticalOffset.

So my suggestion is design a custom control which behavior like a ComboBox, for example I created a UserControl like this:

<Button x:Name="rootButton" BorderBrush="Gray" BorderThickness="2" Click="Button_Click" MinWidth="80" Background="Transparent" Padding="0">
<Grid VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
Width="{Binding ElementName=rootButton, Path=ActualWidth}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="32" />
</Grid.ColumnDefinitions>
<TextBlock Text="{x:Bind selectedItem, Mode=OneWay}" Grid.Column="0" VerticalAlignment="Center" FontSize="15" HorizontalAlignment="Center" />
<FontIcon Grid.Column="1" FontSize="12" FontFamily="Segoe MDL2 Assets" Glyph="" HorizontalAlignment="Right"
Margin="0,10,10,10" VerticalAlignment="Center" />
</Grid>
<FlyoutBase.AttachedFlyout>
<MenuFlyout Placement="Bottom" x:Name="menuFlyout">
<MenuFlyoutItem Text="Item 1" Click="MenuFlyoutItem_Click" />
<MenuFlyoutItem Text="Item 2" Click="MenuFlyoutItem_Click" />
<MenuFlyoutItem Text="Item 3" Click="MenuFlyoutItem_Click" />
<MenuFlyoutItem Text="Item 4" Click="MenuFlyoutItem_Click" />
<MenuFlyoutItem Text="Item 5" Click="MenuFlyoutItem_Click" />
</MenuFlyout>
</FlyoutBase.AttachedFlyout>
</Button>

and the code behind in this UserControl:

public sealed partial class CustomComboBox : UserControl, INotifyPropertyChanged
{
public CustomComboBox()
{
this.InitializeComponent();
selectedItem = "";
}

private string _selectedItem;

public string selectedItem
{
get { return _selectedItem; }

set
{
_selectedItem = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("selectedItem"));
}
}
}

public event PropertyChangedEventHandler PropertyChanged;

private void MenuFlyoutItem_Click(object sender, RoutedEventArgs e)
{
var item = sender as MenuFlyoutItem;
selectedItem = item.Text;
}

private void Button_Click(object sender, RoutedEventArgs e)
{
FlyoutBase.ShowAttachedFlyout(sender as Button);
}
}

And you can use this CustomComboBox in other page like this:

<local:CustomComboBox VerticalAlignment="Center" HorizontalAlignment="Center" />

By default this CustomComboBox will show its DropDown list under it, unless there is no enough space under it to hold this DropDown, in this case, the DropDown will be shown above this CustomComboBox.



Related Topics



Leave a reply



Submit