Reputation: 417
I am building a desktop app using WPF, I need to be able to add views dynamically in a way that will look organized.
I have created an ObservableCollection
, and set itemsControl.ItemsSource
to be that collection.
I am able to add views, it just looks awful.
All I know about the amount of views that can be added, is that there can be a maximum of 16 views.
I thought about creating a dynamic grid, that will change according to the amount of views available, as in the following image
So I tried going with the dynamic-grid idea, I am creating a new grid each time a view is to be added/removed (sounds bad), and by the size of the views collection I know how to split the grid correctly.
Then I loop over all of the existing views in the collection, and place them on the grid by the right logic. I kind of made a real mess here, so if I'm totally in the wrong direction please just let me know.
Here is the code:
public partial class DynamicTargetView : UserControl
{
private ObservableCollection<TargetView> views = new ObservableCollection<TargetView>();
public Grid grid;
public DynamicTargetView()
{
InitializeComponent();
SettingsBar.onViewChange += addOrRemoveTargetsFromPanel;
}
public void addOrRemoveTargetsFromPanel(Object sender, WeiTargetGui.EventArgs.AddOrRemoveViewEventArgs e)
{
if (e.isShown)
{
addTargetToPanel(e.id);
}
else removeTargetFromPanel(e.id);
}
public void addTargetToPanel(string id)
{
views.Add(new TargetView(Int32.Parse(id)));
ArrangeGrid();
}
public void removeTargetFromPanel(string id)
{
foreach (TargetView v in views)
{
if (v.id == Int32.Parse(id))
views.Remove(v);
}
ArrangeGrid();
}
public void ArrangeGrid()
{
int NumOfViews = views.Count();
grid = new Grid();
grid.Children.Clear();
ColumnDefinition gridCol1;
ColumnDefinition gridCol2;
ColumnDefinition gridCol3;
ColumnDefinition gridCol4;
RowDefinition gridRow1;
RowDefinition gridRow2;
RowDefinition gridRow3;
RowDefinition gridRow4;
if (NumOfViews == 1)
{
addtoGrid(0, 0);
}
else if (NumOfViews == 2)
{
gridCol1 = new ColumnDefinition();
gridCol2 = new ColumnDefinition();
grid.ColumnDefinitions.Add(gridCol1);
grid.ColumnDefinitions.Add(gridCol2);
addtoGrid(2, 0);
}
else if (NumOfViews < 5)
{
gridCol1 = new ColumnDefinition();
gridCol2 = new ColumnDefinition();
gridRow1 = new RowDefinition();
gridRow2 = new RowDefinition();
grid.ColumnDefinitions.Add(gridCol1);
grid.ColumnDefinitions.Add(gridCol2);
grid.RowDefinitions.Add(gridRow1);
grid.RowDefinitions.Add(gridRow2);
addtoGrid(2, 2);
}
else if (NumOfViews < 10)
{
gridCol1 = new ColumnDefinition();
gridCol2 = new ColumnDefinition();
gridCol3 = new ColumnDefinition();
gridRow1 = new RowDefinition();
gridRow2 = new RowDefinition();
gridRow3 = new RowDefinition();
grid.ColumnDefinitions.Add(gridCol1);
grid.ColumnDefinitions.Add(gridCol2);
grid.ColumnDefinitions.Add(gridCol3);
grid.RowDefinitions.Add(gridRow1);
grid.RowDefinitions.Add(gridRow2);
grid.RowDefinitions.Add(gridRow3);
addtoGrid(3, 3);
}
else if (NumOfViews < 17)
{
gridCol1 = new ColumnDefinition();
gridCol2 = new ColumnDefinition();
gridCol3 = new ColumnDefinition();
gridCol4 = new ColumnDefinition();
gridRow1 = new RowDefinition();
gridRow2 = new RowDefinition();
gridRow3 = new RowDefinition();
gridRow4 = new RowDefinition();
grid.ColumnDefinitions.Add(gridCol1);
grid.ColumnDefinitions.Add(gridCol2);
grid.ColumnDefinitions.Add(gridCol3);
grid.ColumnDefinitions.Add(gridCol4);
grid.RowDefinitions.Add(gridRow1);
grid.RowDefinitions.Add(gridRow2);
grid.RowDefinitions.Add(gridRow3);
grid.RowDefinitions.Add(gridRow4);
addtoGrid(4, 4);
}
}
public void addtoGrid(int cols, int rows)
{
int row = 0;
int column = 0;
foreach (var view in views)
{
if (cols == 0 && rows == 0)
{
grid.Children.Add(view);
break;
}
if (cols == 2 && rows == 0)
{
Grid.SetColumn(view, column);
column++;
grid.Children.Add(view);
}
else
{
if (column != cols)
{
Grid.SetColumn(view, column);
Grid.SetRow(view, row);
grid.Children.Add(view);
}
if (column < cols)
column++;
else
{
column = 0;
row++;
}
}
}
this.Content = grid;
}
}
TargetView
is a user-control that represents a target with some data about that target.
So here are the problems with the code -
1) When adding more then one component to the views collection I get the following exception:
System.InvalidOperationException: 'Specified element is already the logical child of another element. Disconnect it first.'
I have added the grid.Children.Clear()
- with no help.
2) Final problem - code looks really bad, I just want to make it work so I could learn and implement it better next time.
Help will be appreciated, thanks a lot.
Upvotes: 0
Views: 402
Reputation: 1046
If elements have the same size use UniformGrid instead of Grid, this let you avoid unnecessary calculations
Upvotes: 0
Reputation: 16119
You're going about this completely wrong. You'll save yourself a ton of headache if you just do things properly and use data binding, if not full-blown MVVM.
Whenever you want to display a list of "things" (views or anything else) in WPF you should use an ItemsSource bound to a collection of items containing the logic for the views you want to create. When you do this you'll see what looks like a regular ListBox, with each element being a line of text containing the class name. The trick is to then use DataTemplates to specify what type of view to create for each of your back-end types, i.e.:
<DataTemplate TargetType="{x:Type local:MyViewModelTypeA}">
<view:MyViewTypeA />
</DataTemplate>
<DataTemplate TargetType="{x:Type local:MyViewModelTypeB}">
<view:MyViewTypeB />
</DataTemplate>
Do this and your views will now look correct, but they'll still be in a vertical StackPanel. To change that you can replace the default ItemsPanel:
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
If you want something a bit more "grid-like" then you could also use a UniformGrid, and bind its NumColumns to a property in your parent class that calculates this value at runtime.
There are lots of variations on this and many different parameters to tweak, but that's the general idea. The key to WPF is doing as much logic in your DataContext classes as possible, and bind your front end to it with the most light-weight binding possible.
Upvotes: 1