Horizontal ListBox multiple lines (WPF) - c#

I want to implement a horizontal ListBox, with multiple lines.
Using WrapPanel:
<ListBox>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
I get the following result:
But I am looking for this result:
How can I implement this?

Based on your comments, I made a UniformGrid which adjusts the number of rows/columns, whenever the window is resized.
MainWindow.xaml:
<Grid x:Name="Container">
<ListBox HorizontalContentAlignment="Stretch">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid x:Name="ItemsGrid" Loaded="ItemsGrid_Loaded" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</Grid>
The names and events are important. They are used for adjusting the columns/rows in the code file.
MainWindow.xaml.cs
public partial class MainWindow : Window
{
private UniformGrid itemsGrid;
public MainWindow()
{
InitializeComponent();
}
private void ItemsGrid_Loaded(object sender, RoutedEventArgs e)
{
// Set reference and adjust columns/rows once the UniformGrid is loaded.
itemsGrid = sender as UniformGrid;
((UniformGrid)sender).Columns = (int)(Container.ActualWidth / 250);
((UniformGrid)sender).Rows = (int)(Container.ActualHeight / 75);
}
private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
{
// Adjust number of columns/rows whenevner the window is resized.
if (itemsGrid != null)
{
itemsGrid.Columns = (int)(Container.ActualWidth / 250);
itemsGrid.Rows = (int)(Container.ActualHeight / 75);
}
}
}
This could definitely be cleaned up a bit, but it seems to do the job. There might be more elegant solutions, but I couldn't find any...

Related

WPF: Access transformed Canvas coordinates in code behind

Is there a way in WPF to access the ScaleTransform Parameter of a Canvas Panel (which is created in the xaml File) in the Code Behind File?
Use Case Example:
I want to position a list of items inside a scaled Canvas like so:
<ItemsControl MouseMove="Control_MouseMove">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas>
<Canvas.RenderTransform>
<TransformGroup>
<ScaleTransform>
<ScaleTransform.ScaleX />
</ScaleTransform>
</TransformGroup>
</Canvas.RenderTransform>
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
I also want to display the current position of my items inside the Canvas at Mouse over with a Pop-up. I am currently trying it this way:
public void Control_MouseMove(object sender, MouseEventArgs e) {
if (!this.Popup.IsOpen)
this.Popup.IsOpen = true;
var mousePosition = e.GetPosition(this.SwLane);
this.Popup.HorizontalOffset = mousePosition.X + 10;
this.Popup.VerticalOffset = mousePosition.Y - 20;
this.PopupContent.Text = System.Convert.ToString(mousePosition.X);
}
What I get is the canvas X coordinate inside the Popup (which makes sense to me). However, I would like to display the scale transformed "coordinates" of the canvas.
Is there a way to access the ScaleTransform Parameter in Code Behind so that I can visualise my transformed item position? Or should I do it in a different way? Thanks.
You could set an OnInitialized Handler where you build your transform in code-behind, save its constituent ScaleTransform as a private property and then assign it there as well after your window has initialized
Something crudely like this:
<Window x:Class="WpfApp4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Background="White"
MouseLeftButtonDown="MainWindow_OnMouseLeftButtonDown"
Initialized="MainWindow_OnInitialized"
Title="MainWindow" Height="450" Width="800">
<Grid>
<ItemsControl x:Name="MyItems" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<Rectangle Width="100" Height="200" Stroke="Red" StrokeThickness="5"/>
</Grid>
</Window>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private TransformGroup MyTransform { get; set; }
private ScaleTransform SceneScale { get; set; }
private void MainWindow_OnInitialized(object? sender, EventArgs e)
{
MyTransform = new TransformGroup();
SceneScale = new ScaleTransform(1.0, 1.0, 0, 0);
MyTransform.Children.Add(SceneScale);
MyItems.RenderTransform = MyTransform;
}
}
Now you could add a MouseLeftButtonDown Handler to scale things, for example:
private void MainWindow_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
SceneScale.ScaleX *= 1.1;
SceneScale.ScaleY *= 1.1;
}
For the coordinate thing, expose the current coordinates as properties. Update them when the mouse moves. Bind the UI to them
This might work better if you packaged it all in a control

PreviewMouseDown Routing to Border/Textblock Instead of Button

I'm trying to access the text of a button when I click on it. All the buttons are created by a Data Template. The buttons are inside a UniformGrid which is inside an ItemsControl:
<ItemsControl x:Name="uniformControl">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid MouseDown="Button_PreviewMouseDown">
</UniformGrid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Then in my code behind (my Operations class that has the binding content isn't included):
public MainWindow()
{
InitializeComponent();
uniformControl.PreviewMouseDown += new MouseButtonEventHandler(Button_PreviewMouseDown);
uniformControl.ItemsSource = Operations.operations;
}
private void Button_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
var clickedElement = e.OriginalSource;
Debug.WriteLine(clickedElement);
string keyword = (e.Source as Button).Content.ToString();
Debug.WriteLine(keyword);
}
Although, instead of the OriginalSource being a button, it is either a textblock or a border. I'm assuming this is due to textblock and border being the specific part of the button being clicked, since PreviewMouseDown traces the path to the smallest child element from what I understand.
The line where I assign a value to keyword throws a NullRefernceExcpetion as well.
I've tried using MouseDown instead, but the method didn't even run when I tried that.
Any ideas? Thanks for your time.
You could just use the Button.Click event as an attached event on your UniformGrid, e.g. like this:
<UniformGrid Button.Click="Button_Click">
</UniformGrid>
And then access the button using OriginalSource:
private void Button_Click(object sender, RoutedEventArgs e)
{
var clickedElement = e.OriginalSource;
Debug.WriteLine(clickedElement);
}

WPF: Moving from one cell to the next when pressing Enter in an ItemsControl

I have a WPF control defined as follows:
<ItemsControl ItemsSource="{Binding DutyValueBinders}" IsEnabled="{Binding Enabled}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Value, TargetNullValue=''}" Width="50"></TextBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
This produces a grid, looking like this:
So although there's only one text box defined in the XAML, the user sees a grid.
What I want to do is, after the user has typed a value and pressed Enter or Return, focus should pass to the next cell and so on until the end of the grid is reached. I tried putting this routine into the code behind (copied from another thread on Stackoverflow):
private void UserControl_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key != Key.Enter || (!e.IsToggled && sender is Button)) return;
var request = new TraversalRequest(FocusNavigationDirection.Next);
var keyboardFocus = Keyboard.FocusedElement as UIElement;
if (keyboardFocus == null) return;
keyboardFocus.MoveFocus(request);
e.Handled = true;
}
and modified the header of the XAML as follows:
<UserControl ...
(Other references)
...
d:DesignHeight="300" d:DesignWidth="300" PreviewKeyDown="UserControl_PreviewKeyDown">
The trouble is, as there's only one text box in the XAML definition, there's no tabindex order for it to follow, and so when you press Enter the cursor just vanishes.
Is there any way to do this, short of scrapping the XAML entirely and starting again with a different type of control?
How about handling the PreviewKeyDown event for the ItemsControl like this?:
<ItemsControl ItemsSource="{Binding DutyValueBinders}" IsEnabled="{Binding Enabled}"
PreviewKeyDown="ItemsControl_PreviewKeyDown">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Value, TargetNullValue=''}" Width="50"></TextBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
private void ItemsControl_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
TextBox textBox = Keyboard.FocusedElement as TextBox;
if (textBox != null)
{
textBox.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
}
}

WPF: ListBox item show from bottom to top

I think title is not clear
I am working with WPF and creating custom Messages control. I have message User Control and the message user controls showing in custom Messages control[ListBox].
XAML code:
<ListBox x:Name="uiMessages" ItemsSource="{Binding Path=Messages}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" IsItemsHost="True" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<wpfMessages:MessageDisplay>
</wpfMessages:MessageDisplay>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Current view:
Expected:
Now list box item is showing from top to bottom and after red message there empty space. I will be good whem the 2 green messages will bi in bottom and red one in top. Expected is add messages from botton to top no from top to botton like now.
any Ideas?
Thanks in advance Jamaxack!
It is a little unclear but I think you want to change the ItemsPanel Layout.
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel VerticalAlignment="Bottom"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
Created custom WrapPanel that reverses Elements
XAML code:
<ListBox x:Name="uiMessages" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<wpf:ReverseWrapPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
Code behind:
public class ReverseWrapPanel : WrapPanel
{
public new Orientation Orientation { get { return base.Orientation; } set { base.Orientation = value; ResetAll(); } }
/// <summary>
/// The opposite of the Orientation property.
/// </summary>
Orientation FlipDirection { get { return Orientation == Orientation.Horizontal ? Orientation.Vertical : Orientation.Horizontal; } }
public ReverseWrapPanel()
{
Initialized += ReverseWrapPanel_Initialized;
}
void ReverseWrapPanel_Initialized(object sender, System.EventArgs e)
{
this.Mirror(FlipDirection);
}
protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved)
{
base.OnVisualChildrenChanged(visualAdded, visualRemoved);
foreach (UIElement child in Children.OfType<UIElement>())
{
child.Mirror(FlipDirection);
}
}
void ResetAll()
{
this.Mirror(FlipDirection);
foreach (UIElement child in Children.OfType<UIElement>())
{
child.Mirror(FlipDirection);
}
}
}
Thanx Jamshed!

System.InvalidOperationException when trying yo access UniformGrid.Children in C#

I am populating an UniformGridwith binding. The source is a Square[,], and the UniformGridis filled with Button objects.
Here is what I did :
Data Binding between a double array and a grid
I get a System.InvalidOperationException when trying to do this :
private void OnClickButton(object sender, RoutedEventArgs e)
{
Button b = (Button)sender;
UniformGrid grid = ItemControlGrid.ItemsPanel.LoadContent() as UniformGrid;
int rows = grille.Rows;
int columns = grille.Columns;
UIElementCollection children = grid.Children; // I get the Exception here
int index = children.IndexOf(b);
int row = index / columns;
int column = index % rows;
}
Here is my XAML :
<ItemsControl Background="Gray" Margin="0" Width="800" Height="800"
x:Name="ItemControlGrid"
ItemsSource="{Binding MapGrid}"
ItemTemplateSelector="{StaticResource selector}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid
IsItemsHost="true"
x:Name="My_UniformGrid" Rows="25" Columns="25"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
Why do I get this Exception?
Change the line where you get an exception to:
ItemCollection children = ItemControlGrid.Items;
The rest of the code should compile just as fine. I assume that grille is actually grid.
<ItemsControl Background="Gray" Margin="0" Width="800" Height="800"
x:Name="ItemControlGrid">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid IsItemsHost="true" Rows="25" Columns="25"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- First two elements are just dummies to test the result-->
<Control />
<Image />
<Button Click="Button_Click_1" />
</ItemsControl>
Codebehind:
void Button_Click_1(object sender, RoutedEventArgs e)
{
var b = (Button) sender;
var grid = ItemControlGrid.ItemsPanel.LoadContent() as UniformGrid;
var rows = grid.Rows;
var columns = grid.Columns;
var children = ItemControlGrid.Items;
var index = children.IndexOf(b);
var row = index/columns;
var column = index%rows;
//column will be 2
}

Categories

Resources