wpf autocompletebox default dropdown area is too small - wpf

I set the autocompletebox just like this:
<StackPanel Orientation="Horizontal">
<StackPanel Width="120">
<Label Content="Address"/>
<Controls:AutoCompleteBox x:Name="AddressBox" MaxDropDownHeight="300" Populating="Address_Populating"/>
</StackPanel>
<StackPanel Width="120" Margin="40, 0, 0, 0">
<Label Content="Port"/>
<TextBox x:Name="PortBox" />
</StackPanel>
<Button x:Name="ConnectButton" Content="Connect" Margin="40, 0, 0, 0" VerticalAlignment="Bottom" Width="80" Height="35" Click="ConnectButton_Clicked"/>
</StackPanel>
But the max number of items displayed in the dropdown window is only 3. I am sure that the candidate number is larger than 3. I want to increase the number of items which will be displayed in the dropdown window.
For example, i want to show 15 candidateAddress's items. And the dropdown window will be displayed and 3 items will be showed firstly. But I hope it can show 5 items firstly, which means that the display area should be expanded.
The logical code of this control is:
private void Address_Populating(object sender, PopulatingEventArgs e)
{
string dirFile = "../../Config/Address.config";
if (File.Exists(dirFile))
{
var candidateAddress = new List<string>();
string input = null;
using (StreamReader sr = File.OpenText(dirFile))
{
while ((input = sr.ReadLine()) != null)
{
candidateAddress.Add(input);
}
}
AddressBox.ItemsSource = candidateAddress;
AddressBox.PopulateComplete();
}
else
{
System.Windows.MessageBox.Show("Address.config does not exist");
}
}

i can't add a pic to comment. so i add it here.
top pic : MaxDropDownHeight=50
bottom: MaxDropDownHeight=300
you mean like this?
you can creat a new project:
<Grid>
<control:AutoCompleteBox x:Name="AddressBox" FontSize="30" MaxDropDownHeight="300"
Populating="Address_Populating" Margin="0,0,0,287.283" />
</Grid>
background code:
private void Address_Populating(object sender, PopulatingEventArgs e)
{
List<int> lst = new List<int>();
for (int i = 10; i < 25; i++)
{
lst.Add(i);
}
AddressBox.ItemsSource = lst;
AddressBox.PopulateComplete();
}
the reason is found by kylejan:
i found the reason. the display area will be influenced by the former window. for example, the window i put this control on is opend by the main window. then this autocomplete box will be influenced by the size of the main window

There is no easy way (included in the Framework) to exactly set the number of items to be displayed. But this example gives you some code to create an attached property that manipulates the maxheight property in order to display any specified number of items.

Related

WPF grid — resize columns in certain order

I have a problem with WPF grid. Need some help.
I have a grid splitted in two columns (e.g. "col1" and "col2" from left to right). This grid is bound to the window edges with no padding:
|--- col1 ---|--- col2 ---| ← right window border
How can I make these columns resize together with the app window in certain order?
I mean this:
when I shrink the window from the right border I need col2 to resize in first place. When col2 reaches its MinWidth — then col1 begins to shrink (I continue to move the right border of the window).
Is it possible to define the order in which columns change their size?
Or may be I need something else but the grid?
Thanks.
Understanding how to handle events and fire off your own is a cornerstone of understanding C# so I highly recommend you read up on it. That being said, this will get you started, although I'm not sure this is exactly what you want... See example code below. I leave writing code for when the window width is increased as an exercise to you.. Good luck =).
On my MainWindow I set Height="250" Width="600" and the Grid inside it gets two columns...
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="_col1" MinWidth="200" />
<ColumnDefinition x:Name="_col2" MinWidth="150"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="_txtCol1Width"
Grid.Column="0"
Background="Crimson" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
<TextBlock x:Name="_txtCol2Width"
Grid.Column="1"
Background="Turquoise" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
</Grid>
x
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.Loaded += MainWindow_Loaded;
this.SizeChanged += MainWindow_SizeChanged;
}
void MainWindow_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (this.IsLoaded && e.WidthChanged)
{
double changeInWindowWidth = e.NewSize.Width - e.PreviousSize.Width;
if (changeInWindowWidth < 0)
{
if (_col2.ActualWidth + changeInWindowWidth >= _col2.MinWidth)
{
// col 2 has not yet reached its minimum (decrease col2, no change for col1)
_col2.Width = new GridLength(_col2.ActualWidth + changeInWindowWidth, GridUnitType.Pixel);
_col1.Width = new GridLength(_col1.ActualWidth + 0, GridUnitType.Pixel);
}
else if (_col1.ActualWidth + changeInWindowWidth >= _col1.MinWidth)
{
// col 2 has reached its minimum, but col1 has not (decrease col1, no change for col2)
_col1.Width = new GridLength(_col1.ActualWidth + changeInWindowWidth, GridUnitType.Pixel);
_col2.Width = new GridLength(_col2.ActualWidth + 0, GridUnitType.Pixel);
}
else
{
// both columns have reached their minimum, so decrease width of both equally
_col1.MinWidth = _col1.ActualWidth + 0.5 * changeInWindowWidth;
_col2.MinWidth = _col2.ActualWidth + 0.5 * changeInWindowWidth;
_col1.Width = new GridLength(_col1.MinWidth, GridUnitType.Pixel);
_col2.Width = new GridLength(_col2.MinWidth, GridUnitType.Pixel);
}
}
else
{
// todo: handle window width increased ...
}
UpdateTexts();
}
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
UpdateTexts();
}
private void UpdateTexts()
{
_txtCol1Width.Text = String.Format("column {0}\nActualWidth: {1}\n(MinWidth: {2})", 1, _col1.ActualWidth, _col1.MinWidth);
_txtCol2Width.Text = String.Format("column {0}\nActualWidth: {1}\n(MinWidth: {2})", 2, _col2.ActualWidth, _col2.MinWidth);
}
}

DatagridTemplate column content only visible when row selected and cell clicked

Hi I have a Datagrid that is bound to an ObservableCollection of custom AutoCAD layer objects. 3 of the columns are DataGridTextColumns and work correctly. However I also have a DataGridTemplateColumn that contains a StackPanel containing a label and a Rectangle. I am using the label to display the ACI or RGB value of the layer depending how it is set and displaying the colour in the rectangle. The rectangle has a mouse down event that launches a colour picker dialog so the user can select a new colour for the layer. This functionality works. What doesn't work is that the contents of the cell (the label and rectangle) are only shown in a row that is selected and the cell clicked on whereas they need to be visible at all times.
I have tried using a Grid inside the DataTemplate and using the Grid's FocusManager.Focused element to give the Rectangle Focus but this hasn't changed the behaviour.
<t:DataGrid x:Name="layersGrid" ItemsSource="{Binding Layers}"
SelectedItem="{Binding SelectedLayer, Mode=TwoWay}" SelectionMode="Single">
<t:DataGridTemplateColumn Visibility="Visible">
<t:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<Grid FocusManager.FocusedElement="{Binding ElementName=swatch}">
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Colour.ColourProperty}"/>
<Rectangle Name="swatch" Fill="{Binding Colour, Converter={StaticResource colourConverter}}"
MouseLeftButtonDown="swatch_MouseLeftButtonDown"/>
</StackPanel>
</Grid>
</DataTemplate>
</t:DataGridTemplateColumn.CellEditingTemplate>
</t:DataGridTemplateColumn>
</t:DataGrid.Columns>
</t:DataGrid>
Additionally, once you change the colour of the layer in the model view, the rectangle hasn't updated until another row is selected and then the changed one is selected again.
private void swatch_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Colour col = LaunchColourPickerCode();
((LayersModel)this.Resources[MODEL]).SelectedLayer.Colour = col;
}
The problem with them not displaying has been fixed by using CellTemplate instead of a CellEditingTemplate
I adapted surfen's answer on this page to solve the selection problem
How to perform Single click checkbox selection in WPF DataGrid?
Replacing his method with this:
private static void GridColumnFastEdit(DataGridCell cell, RoutedEventArgs e)
{
if (cell == null || cell.IsEditing || cell.IsReadOnly)
return;
DataGrid dataGrid = FindVisualParent<DataGrid>(cell);
if (dataGrid == null)
return;
if (!cell.IsFocused)
{
cell.Focus();
}
DataGridRow row = FindVisualParent<DataGridRow>(cell);
if (row != null && !row.IsSelected)
{
row.IsSelected = true;
}
}
and adding an event on the swatch to obtain the cell it is in
private void swatch_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
DataGridCell cell = null;
while (cell == null)
{
cell = sender as DataGridCell;
if (((FrameworkElement)sender).Parent != null)
sender = ((FrameworkElement)sender).Parent;
else
sender = ((FrameworkElement)sender).TemplatedParent;
}
GridColumnFastEdit(cell, e);
}
Also thanks to kmatyaszek

Can a WPF ListBox's height be set to a multiple of its item height?

Is there some way to set the Height attribute of a WPF multi-select ListBox to be a multiple of the item height, similar to setting the size attribute of an html select element?
I have a business requirement to not have half an item showing at the bottom of the list (if it's a long list with a scrollbar), and not have extra white space at the bottom (if it's a short list with all items showing), but the only method I can find to do this is to just keep tweaking the Height until it looks about right.
(What else have I tried? I've asked colleagues, searched MSDN and StackOverflow, done some general Googling, and looked at what VS Intellisense offered as I edited the code. There's plenty of advice out there about how to set the height to fit the ListBox's container, but that's the opposite of what I'm trying to do.)
Yeah, one could imagine there would be an easier way to do it (a single snapToWholeElement property). I couldn't find this property as well.
To achieve your requirement, I've wrote a little logic. Basically, In my Windows object I've a public property lbHeight which is calculate the listbox height by calculating the height of each individual item.
First, let's take a look at the XAML:
<Window
x:Class="SO.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="120" SizeToContent="Height"
Title="SO Sample"
>
<StackPanel>
<ListBox x:Name="x_list" Height="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}, Path=lbHeight}" >
<ListBox.ItemTemplate>
<DataTemplate>
<Border x:Name="x" Background="Gray" Margin="4" Padding="3">
<TextBlock Text="{Binding}" />
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Window>
Note that the ItemTemplate is somewhat non trivial. One important thing to notice is that I gave this item a Name - so I can find it later.
In the code-behind constructor I put some data in the list box:
public MainWindow( )
{
InitializeComponent( );
this.x_list.ItemsSource = Enumerable.Range( 0, 100 );
}
next, I'm implementing a findVisualItem - to find the root element of the data template. I've made this function a little generic, so it get a predicate p which identify whether this is the element I want to find:
private DependencyObject findVisualItem( DependencyObject el, Predicate<DependencyObject> p )
{
DependencyObject found = null;
if( p(el) ) {
found = el;
}
else {
int count = VisualTreeHelper.GetChildrenCount( el );
for( int i=0; i<count; ++i ) {
DependencyObject c = VisualTreeHelper.GetChild( el, i );
found = findVisualItem( c, p );
if( found != null )
break;
}
}
return found;
}
I'll use the following predicate, which returns true if the element I'm looking for is a border, and its name is "x". You should modify this predicate to match your root element of your ItemTemplate.
findVisualItem(
x_list,
el => { return ( el is Border ) ? ( (FrameworkElement)el ).Name == "x" : false; }
);
Finally, the lbHeight property:
public double lbHeight
{
get {
FrameworkElement item = findVisualItem(
x_list,
el => { return ( el is Border ) ? ( (FrameworkElement)el ).Name == "x" : false; }
) as FrameworkElement;
if( item != null ) {
double h = item.ActualHeight + item.Margin.Top + item.Margin.Bottom;
return h * 12;
}
else {
return 120;
}
}
}
I've also made the Window implementing INotifyPropertyChanged, and when the items of the list box were loaded (Loaded event of ListBox) I fired a PropertyChanged event for the 'lbHeight' property. At some point it was necessary, but at the end WPF fetched the lbHeight property when I already have a rendered Item.
It is possible your Items aren't identical in Height, in which case you'll have to sum all the Items in the VirtualizedStackPanel. If you have a Horizontal scroll bar, you'll have to consider it for the total height of course. But this is the overall idea. It is only 3 hours since you published your question - I hope someone will come with a simpler answer.
This is done by setting parent control Height property to Auto, without setting any size to the Listbox itself (or also setting to Auto).
To limit the list size you should also specify MaxHeight Property

How to change Text of TextBlock which is in DataTemplate of Row Details for each DataGrid Row Details?

I have Datagrid which is clicking by mouse in each row is showing data grid row details. here is code,
Microsoft.Windows.Controls.DataGridRow row = (Microsoft.Windows.Controls.DataGridRow)(DataGrid1.ItemContainerGenerator.ContainerFromItem(DataGrid1.SelectedItem));
DataGridDetailsPresenter presenter = FindVisualChild<DataGridDetailsPresenter>(row);
DataTemplate template = presenter.ContentTemplate;
TextBlock txt = (TextBlock)template.FindName("rowdetails", presenter);
txt.Text = retString;
And also I have Checkbox, when you check it, it should show all row details.
I am trying this code for showing all rowdetails
if ((bool)chkboxRowDetails.IsChecked)
{
DataGrid1.RowDetailsVisibilityMode = Microsoft.Windows.Controls.DataGridRowDetailsVisibilityMode.Visible;
for (int i = 0; i < DataGrid1.Items.Count-1; i++)
{
Microsoft.Windows.Controls.DataGridRow row = (Microsoft.Windows.Controls.DataGridRow)(DataGrid1.ItemContainerGenerator.ContainerFromIndex(i));
DataGridDetailsPresenter presenter = FindVisualChild<DataGridDetailsPresenter>(row);
DataTemplate template =presenter.ContentTemplate;
TextBlock txt = (TextBlock)template.FindName("rowdetails", presenter);
txt.Text = retString;
}
But it is giving error. "This operation is valid only on elements that have this template applied."
Showing in line TextBlock txt = (TextBlock)template.FindName("rowdetails", presenter);
Do you have any idea what is wrong in my code. I want to show all row details by checking checkbox. My Data template is here
<WpfToolkit:DataGrid.RowDetailsTemplate>
<DataTemplate>
<StackPanel HorizontalAlignment="Stretch" Orientation="Vertical" Margin="5">
<TextBlock Foreground="CadetBlue" FontSize="14"
TextWrapping="Wrap" Name="rowdetails" HorizontalAlignment="Stretch"
/>
</StackPanel>
</DataTemplate>
</WpfToolkit:DataGrid.RowDetailsTemplate>
I solved this problem by adding some codes in row details load. Here is code.
TextBlock txt1 = e.DetailsElement.FindName("rowdetails") as TextBlock;
txt1.Text = retString; // where retString is variable string.

How to use from one short key on active tab?

I have a window that has some tabs,in each tab i can create a new item.
I want define a short key for create new item.But i want my short Key work on active tab.
For example, when Tab1 was active my short key work on create item in Tab1 or when Tab2 was active my short key work on create item in Tab2. How can i use from one short key on active tab?
There are many ways to accomplish this. The most common is to use a command. First, here's the XAML I used:
<Grid>
<TabControl Grid.Row="0"
x:Name="AppTabs">
<TabItem Header="Tab 1">
<ListBox x:Name="TabOneList" />
</TabItem>
<TabItem Header="Tab 2">
<ListBox x:Name="TabTwoList" />
</TabItem>
</TabControl>
</Grid>
Here's the code-behind:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// create the new item command and set it to the shortcut Ctrl + N
var newItemCommand = new RoutedUICommand("New Item", "Makes a new item on the current tab", typeof(MainWindow));
newItemCommand.InputGestures.Add(new KeyGesture(Key.N, ModifierKeys.Control, "Ctrl + N"));
// create the command binding and add it to the CommandBindings collection
var newItemCommandBinding = new CommandBinding(newItemCommand);
newItemCommandBinding.Executed += new ExecutedRoutedEventHandler(newItemCommandBinding_Executed);
CommandBindings.Add(newItemCommandBinding);
}
private void newItemCommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
{
// one way to get the ListBox control from the currently selected tab
ListBox itemList = null;
if (AppTabs.SelectedIndex == 0)
itemList = this.TabOneList;
else if (AppTabs.SelectedIndex == 1)
itemList = this.TabTwoList;
if (itemList == null)
return;
itemList.Items.Add("New Item");
}
I wouldn't consider this production code, but hopefully it points you in the right direction.

Resources