Storing and displaying a grid of buttons / multiple layouts - c#

I am building a point of sale application using WPF with MVVM.
One of the features is that there will be a grid/panel on the screen with say for example 10 x 10 buttons, 1 button per stock item for example.
What I need to do is to allow the user to create a new layout or edit an existing layout where they can add a new button or remove a button etc..
So in theory the user can have a number of different layouts where they can select from to show on the screen.
Each of the buttons will be linked to a stock product in the database and will have properties like colour, text, image etc..
So I am thinking if I have a user control set to 10 x 10 buttons or use a Uniform Grid, I could bind to a collection of buttons. I was also thinking of storing each layout button for each layout as XML and reading this to create some kind of collection and binding that to the grid.
For example the user could at point have the choice of choosing between 10 different screen/ grid layouts, each layout having its own set of buttons which could be in different positions, or have different text etc..
My question is, is this the best approach to store the buttons and layouts as XML?
And how could i directly bind a grid/uniform grid to a collection of buttons?
Many thanks

Overall approach looks fine to me. As for binding button collection to grid, you can use ItemsControl with custom ItemsPanel template for this:
<ItemsControl ItemsSource="{Binding Buttons}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="10" Columns="10" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button>
<!-- bind button color, content, etc -->
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

Related

Wrap the same control in a WrapPanel

in my WPF project I have a selfmade timeline visualization. Each row of a Grid is an hour (e. g. 9 am, 10 am, and so on) and from left to right, the minutes increase from 0 to 60.
Assume an event ranges from 9:50 pm to 10:30 pm. It is visualized by some custom control, that fits its Width and position to the corresponding time in the "timeline". Can I break the same control to continue in the next row? I know a Wrap panel does this, but only between two separate controls.
Thanks in advance.
Your custom control probably consists of other controls, right? And GridView is probably not the best choice if you need wrapping, you cannot move some columns to the new "line". Instead you could visualize each event as e.g. StackPanel with some custom stuff etc. So you should basically include the break-line logic into your control, so that when your control doesn't get enough width it would move its internal elemnets to a new line. How about including a WrapPanel into your control?
You could also use a normal ListView if you want to bind to a collection of elements and redefine the internal collection control of the ListView, so that it positions items horizontally. So all your events would stack to each other horizontally and wrap when needed:
<MyCustomControl>
<ListView ScrollViewer.HorizontalScrollBarVisibility="Disabled" ItemsSource="{Binding ... my events ...}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
...
</ListView>
</MyCustomControl>

WPF: How to dynamically create a grid with x rows and y columns with consecutive numbers

I am completely new to wpf and c#, so excuse if this is super trivial question. I am trying to create a fairly simple control.
This grid will always have consecutive numbers, with a color rectangle in front of it. Clicking on the gray rectangle will change its color, and set the text to bold (I will deal with these triggers later).
For now, I just need to figure out how to create this control dynamically. When the program starts, it needs to one time create this control, and then the size won't change. I need to tell it the number of columns and rows (each column will probably always have 8 elements), and have it populate with consecutive numbers with specific font style/rectangle color.
I was experimented with creating a stackpanel UserControl for rectangle/label combo, passing the style to it, and then adding 32 of these UserControls in specific row/column in a grid. But I would need the size of that grid to be dynamic, so I need some for loop in the code I think.
Thanks!
I would start with an ItemsControl
You can give it a collection of items, and it will render each item however you want, displayed in any panel you want.
For example, you might have something like this
<ItemsControl ItemsSource="{Binding MyCollection}">
<!-- This panel will be used to hold the items -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="8" Columns="8" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- Each item will be drawn using this template -->
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Text="{Binding }" Style="{StaticResource MyButtonStyle}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The Rows and Columns property of the UniformGrid are DependencyProperties, so you could bind them to properties on the DataContext to make them dynamic.
The only problem with a UniformGrid is it only arranges items Horizontally. If you want to display them Vertically, you can either create a custom UniformGrid, or switch to a different panel such as a WrapPanel. If you are new to WPF Panels, I would recommend reading through WPF Layouts - A Quick Visual Start.
The ItemTemplate can be anything. Personally I would use a Button so you have the Click or Command behavior to handle that event, and just overwrite the Button's Template to look however you want. It is an easy task to include your Triggers in there too.
And if you wanted selection behavior, I would recommend switching from an ItemsControl to a ListBox, and overwriting that Template the same way, however it doesn't sound like you need it though, so I think an ItemsControl is better :)
I would try using a listview and change the template to the style you want to use for your elements.
To limit the number of items in a row you can use
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="3" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
That way you would always get 3 elements in a row, like
123
456
To make the 3 dynamic you can databind it to some value in your codebehind / viewmodel
to dynamically create the elements within the listview you can add objects to a list/observable collection and then add those to the listview via
listviewname.ItemSource=ListName;
Or however you like. They will get arranged according to how many columns you tell the grid to have. Adding 32 items (with uniform grid of 4) leads to
1 2 3 4
5 6 7 8
9 10 11 12
...
On your page you must create a "main" element, for example a Grid.
Give it a name, so that we can access it by code. Here I gave it the name of root
So you will have something like
<Page
... >
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
x:Name="root">
</Grid>
</Page>
Then, on the .cs file of this page you must create a function with the code below. You can call this function on the MainPage() function.
This loop will create one Grid column with dynamic Grid rows
// Create a Grid and add it to a component of your page
Grid mainGrid = new Grid();
root.Children.Add(mainGrid);
for (int i = 0; i < 8; i++)
{
// I am creating a Grid with a TextBlock inside,
// it will have the same apperance as a Rectangle,
// but this way you can have a Text inside
Grid g = new Grid();
TextBlock tb = new TextBlock();
tb.Text = i.ToString();
g.Children.Add(tb);
// Here you set the Grid properties, such as border and alignment
// You can add other properties and events you need
g.BorderThickness = new Thickness(1);
g.BorderBrush = new SolidColorBrush(Colors.Black);
g.HorizontalAlignment = HorizontalAlignment.Stretch;
g.VerticalAlignment = VerticalAlignment.Stretch;
// Add the newly created Grid to the outer Grid
mainGrid.RowDefinitions.Add(new RowDefinition());
mainGrid.Children.Add(g);
// Set the row of the Grid.
Grid.SetRow(g, i);
}
I used a Grid instead of a Rectangle since Rectangles can't have Children.
It must be easy to create the other columns as well, using the same logic that I used to create the Rows.
The solution to me was pretty like the ones above, but I had to bind the ItemsSource in the back code.
<!-- This panel will be used to hold the items -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- Each item will be drawn using this template -->
<ItemsControl.ItemTemplate>
<DataTemplate>
<!--Here is the big deal I think. Desing your template according to what you need. In this case, a grid with 1 default row and column will work-->
<Grid>
<!--Reading object property called "Exchange" and setting its value into a text block-->
<TextBlock
Margin="10, 10, 10, 40" FontSize="16"
HorizontalAlignment="Center"
VerticalAlignment="Center" Foreground="GreenYellow" Text="{Binding Exchange}" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
Then in the page.cs I did the binding right in the constructor. But you can bind anytime suits you
List<Bot> Bots = new()
{
new Bot
{
Exchange = "Binance"
},
new Bot
{
Exchange = "Kukoin"
}
};
ItemsControlName.ItemsSource = Bots; // I HAD to bind here. Could not make it work another way.
In my example, I have used a list of a simple class called Bot, with one single property called Exchange. Like this
public class Bot
{
public string Exchange { get; set; }
}
Then I got it:

Changing ItemsPresenter in custom ComboBox style to display items next to each other

I want ComboBox to contain Images as items (they will be added programatically). In default ComboBox they are displayed vertically. I would like to have them to be displayed like in VariableSizedWrapGrid but I'm not exactly sure how to do it.
I have this ItemsPresenter for ComboBox style but changing it to anything else results in nothing being displayed.
Or maybe I should make it other way (using ListView?). I want selected item to be displayed like in ComboBox and after click all items displayed at once (around 25).
How it looks now:
What I want:
Change the ItemsPanel property:
<ComboBox>
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
</ComboBox>
If you want to use something like VariableSizedWrapGrid you can create your own implementation or check out other peoples work for example: VariableSizedWrapGrid for WPF.
I've made some research and it seems it's not possible to achieve what I want. I'll just do it another way to get what I want (Button with Flyout and ListView inside it should do the work).

How to arrange the buttons vertically in RibbonControlGroup (wpf)

I have RibbonControlGroup with a few buttons.
<r:RibbonControlGroup>
<r:RibbonToggleButton Label="button1"/>
<r:RibbonToggleButton Label="button2"/>
</r:RibbonControlGroup>
On Ribbon buttons are arranged horizontally. Like (button1|button2).
How to arrange the buttons vertically?
I know, that I'm late to party, but may be this will help someone.
Since the RibbonControlGroup is an ItemsControl, the right way to go is to replace ItemsPanel like this:
<r:RibbonControlGroup>
<r:RibbonControlGroup.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</r:RibbonControlGroup.ItemsPanel>
<!-- Group items are listed below: -->
<r:RibbonToggleButton Label="button1"/>
<r:RibbonToggleButton Label="button2"/>
</r:RibbonControlGroup>
This approach conceptually differs from #Sajeetharan's answer, because he creates a group with a single child - StackPanel, while my sample still is an equivalent of the code, posted in the question: here's a group with two children of type RibbonToggleButton.
The difference will be meaningful with data binding scenarios, when items of group be populated dynamically through data binding expression.
You can keep these controls in a container and set its Orientation property to "Vertical"
For e.g. using StackPanel as a container
will place the buttons vertically
Use StackPanel and change the orientation to Vertical ,
<r:RibbonControlGroup>
<StackPanel Orientation="Vertical">
<r:RibbonToggleButton Label="button1"/>
<r:RibbonToggleButton Label="button2"/>
</StackPanel>
</r:RibbonControlGroup>

Multiple grids in one area

I'm currently creating a WPF application, using C# and XAML in Visual Studios 2010.
I have a master grid. In that master grid I have a group bar which you can select different items. Depending on what you select, the middle of the master grid can be totally different. What I was wondering is, what's the best way to program the middle part?
Right now, I have it set up in such a way that everything in the middle is dynamically programed in C#, and everything on the outside is programmed in XAML.
In C# I programmed: for each group bar item, there is a grid that goes with it (so that different content can be displayed on it). Each grid is a child of the master grid. Each grid is visible or hidden when necessary. Is this the best way to approach this?
The best example of this is in Outlook 2007, where you have your group bar on the right hand side. When you select different items on the group bar (mail, calendar, tasks) the right of the group bar completely changes.
The easy way to do this in WPF is to define DataTemplates for each of your "middle" sections.
Using the Outlook example, you might have a MessageCollection class that stores a list of messages, an EventCollection class that stores a list of calendar events, and a TaskCollection class that stores a list of tasks.
In your "middle" area you would simply have a single ContentPresenter whose Content would be set to a MessageCollection, EventCollection, or TaskCollection. Presumably this would be done using a binding to a view model property.
Here is how it might look:
<Window ...>
<Grid>
<!-- group bar area -->
...
<!-- "middle" area -->
<ContentPresenter Grid.Row="1" Grid.Column="1"
Content="{Binding SelectedCollection}" />
</Grid>
</Window>
Now you create a DataTemplate for each of the collection types, for example:
<DataTemplate TargetType="{x:Type my:MessageCollection}">
<Grid>
... put the XAML for displaying mailbox contents here ...
</Grid>
</DataTemplate>
<DataTemplate TargetType="{x:Type my:EventsCollection}">
<Grid>
... put the XAML for displaying a calendar here ...
</Grid>
</DataTemplate>
<DataTemplate TargetType="{x:Type my:TasksCollection}">
<Grid>
... put the XAML for displaying a to-do list here ...
</Grid>
</DataTemplate>
With this setup, all you have to do to switch the inner grid is to set your "SelectedCollection" property in your view model to a different collection type.

Categories