WPF Databinding vs Programmatically generating controls - c#

My underlying data structure consists of a hierarchy of objects that are built upon a common abstract class:
public abstract class model {
public string name {get;};
public string type {get;};
}
public class A:model {
public int val1 {get; set;};
}
public class B:model {
public int val2 {get; set;};
}
public class C:B {
public Dictionary<string, int> extra_props;
}
My goal is to create a UI that upon object selection, is able to dynamically preview but also be able to modify the object's underlying properties.
I'm an absolute noob regarding WPF, so I have no idea what its full capabilities are. For now regarding the single value properties, I have found my way to use Databinding to bind the properties to the UI elements. It works like a charm for showing and also modifying the values.
<GroupBox x:Name="InfoBox" Header="Object Information">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="41*"/>
<ColumnDefinition Width="200*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition Height="20"/>
<RowDefinition Height="20"/>
<RowDefinition Height="50*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<DockPanel Grid.Row="0" Grid.ColumnSpan="2" >
<TextBlock>Name:</TextBlock>
<TextBlock Text="{Binding name}" HorizontalAlignment="Right"></TextBlock>
</DockPanel>
<DockPanel Grid.Row="1" Grid.ColumnSpan="2" >
<TextBlock>Type:</TextBlock>
<TextBlock Text="{Binding type}" HorizontalAlignment="Right"></TextBlock>
</DockPanel>
<DockPanel Grid.Row="2" Grid.ColumnSpan="2" >
<TextBlock>Material Name:</TextBlock>
<TextBlock Text="{Binding val1}" HorizontalAlignment="Right"></TextBlock>
</DockPanel>
</Grid>
</GroupBox>
Problem 1:
When an object of type B is bound, property val1 doesn't exist and I'm getting some BindingErrors in the debug output. These cause no problem during execution at all, but I have found no way of catching them and returning a default value or something.
The only way that I have thought of for solving this issue is to add all the desired properties in the abstract class so that they all exist on all derived classes with some null values or something, but this doesn't feel right at all.
Problem 2:
For more complex classes like C, I want to dynamically generate the UI based on a set/list of properties of the class. For now I have absolutely no idea how to do it with databinding, except for adding them all one by one in the XML and working around the issues of problem 1.
The most viable solution that I thought of is to programmatically generate a Control and add it to the main window with textboxes and inputs for the class properties that I need, and again programmatically hopefully be able to bind the object to the control so that the values are read/set appropriately. Obviously this method would resolve problem 1 as well.
I'm not even sure if this solution is possible or not, but in any case I need recommendations and advice on mainly if there is a way to resolve my issues with data-binding or if I should go with with programmatically updating the UI (if possible).

Well for a noob you're doing well so far :) There's actually a very simple solution to both of these problems: DataTemplates. You can read all about them on the microsoft site.
In your case you want to declare a separate template for each type you're trying to display:
<DataTemplate DataType="{x:Type local:A}">
<TextBlock Text="This object is type A">
</DataTemplate>
<DataTemplate DataType="{x:Type local:B}">
<TextBlock Text="This object is type B">
</DataTemplate>
...and so on. This is typically done in the Resources block of whatever window/user control that this code appears on, although it can also be declared in the App.xaml resources block etc.
DataTemplates are used by any WPF control that is able to bind to data in some way. Take Button, for example....the overall look of the button including the border and how it behaves to mouse-over etc is dictated by its ControlTemplate (which you can also override yourself of course). But at some point in the XAML it has to render the actual data that you've assigned to its "Content" property, and if you were to look at the ControlTemplate for Button you'd find something like this buried inside it:
<ContentPresenter />
This effectively says "ok, this is where my actual data should be rendered, but instead of declaring it specifically here I want you to refer to the object's corresponding DataTemplate instead". In this way you can create a top-level button style using a single ControlTemplate, but you can then specify multiple DataTemplates for each of the types of things that you'll render inside it.
Lots of controls use DataTemplate, including things like ListBox etc where each element in the list can be given a different graphical representation based on its type. Going back to your own specific problem, if you just want to render the data itself without any bells and whistles etc around it then just use a ContentControl:
<ContentControl Content="{Binding MyModel}" />
So MyModel should be a property of type model that's in your view model layer (or whatever else you've set the DataContext to). Assigning that property to be an instance of type A, B or C will cause the ContentControl to be populated with an instance of whatever you've declared in its DataTemplate.

Related

Access template properties which are stored in resources

I have a derived devexpress grid control which sets some template for the indicator row:
<dxg:GridControl.Resources>
<sys:Double x:Key="{dxgt:TableViewThemeKey ResourceKey=IndicatorWidth, ThemeName=Office2016White}">300</sys:Double>
<DataTemplate x:Key="{dxgt:RowIndicatorThemeKey ResourceKey=RowTemplate, ThemeName=Office2016White}">
<Grid Name="IndicatorGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="35" Name="IndicatorColumnRowNumber" SharedSizeGroup="RowNumberGroup" />
<ColumnDefinition Width="200" Name="IndicatorColumnDescription" SharedSizeGroup="DescriptionGroup" />
<ColumnDefinition Width="200" Name="IndicatorColumnSource" SharedSizeGroup="SourceGroup" />
</Grid.ColumnDefinitions>
//...
</DataTemplate>
</dxg:GridControl.Resources>
Right now the width of the indicator column is fixed to 300. Now I want the width of the row being calculated by the columns defined in the second part. I know I can access the indicator width of the view via the code behind as well but I'm not able to access the template controls in the code behind
var view = ((TableView)this.View);
view.IndicatorWidth = IndicatorColumnRowNumber.Width /* can not be found */ + ...
Since they live in the resources in the xaml. As I understand this is also not supposed to happen. What is the best way to implement this? Maybe extract the definitions in the code behind?
Edit:
Here is a picture of the table
I am not 100% sure what you are trying to achieve but if you want to access your resource in code behind you can go about it like this:
XAML:
<Grid x:Name="MyGrid">
<Grid.Resources>
<DataTemplate x:Key="MyResource">
<TextBlock Text="Hello"></TextBlock>
</DataTemplate>
</Grid.Resources>
</Grid>
Code behind:
public MainWindow()
{
InitializeComponent();
var resource = MyGrid.Resources["MyResource"];
var dataTemplate = (DataTemplate) resource;
}
You have additional tools to obtain any defined resources.
The methods FrameworkElement.FindResource and FrameworkElement.TryFindResource will search for resources from the element you specified and up the visual tree until the main application and including any themes you have setup. From the MS reference:
FrameworkElement.FindResource documentation
Also notice that the Resources dictionary and the FindResource methods accept an object key (not a string). This suits your case since your key is a RowIndicatorThemeKey object. You can instantiate such an object and set the ResourceKey property to the "RowTemplate" value and search for your resource like that.
In general solutions for problems such as yours can be achieved without requiring code behind though, if you provide some more information perhaps we can find a solution based on bindings.

Boardgame with dynamically created grid and a control inside to use drag and drop

For school I have to create a boardgame of my choice. I chose a simple boardgame where you have to combine blocks of different colors in a way to gain most points out of it. You can have 2 to 4 players.
Until now I build a WPF application with MVVM and PRISM. The user is pressing a button to decide the amount of players and based on that I want to create a grid dynamically in XAML (because of MVVM and no code-behind) with sizes of 6x6, 7x7 and 8x8. In each cell there need to be a control (I don't know which one) which can hold a visual block.
My idea is to create that grid, put a ListBox in each cell, allow just one item for the ListBox and drag and drop a Rectangle on it.
For the dynamically created grid I read about using ItemsControl to build it up. Here a short code example, which isn't working:
<ItemsControl Grid.Row="1" Grid.Column="1" ItemsSource="{Binding Path=GameBoard}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid ShowGridLines="True" Row="{Binding Path=Rows}" Column="{Binding Path=Columns}">
<Grid.RowDefinitions>
<RowDefinition MinHeight="20"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="20"/>
</Grid.ColumnDefinitions>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Inside I could place a ListBox to hold that block I was talking about. For my understanding I need to have a property bound to every single cell/ListBox to actually hold it and work with it in code.
There is plenty of stuff on the internet, but I couldn't wire it together to get it working. Do you have a point to start for me?

Programmatically moving TextBlocks in WPF

I have been reading some tutorials on XAML but it does not help me. I have an empty application window and I need to create 30 TextBoxes in 3 rows.
Being used on the win forms, I thought I would figure it out - well, I did not. I cannot seem to find a way how to create them on certain coordinates.
You first want to place a Canvas control on your screen, then you can populate it with TextBoxes placed at whatever Canvas.Left and Canvas.Top position you want.
That said though, WPF has a much better layout/arrangement system than WinForms, and trying to use it like it's WinForms means you'll miss out on a lot of what makes WPF so great, and you'll be making things a lot harder on yourself.
The WPF way of doing the same thing would be to use an ItemsControl, and a collection of objects that each contain data that the UI needs to to know for display purposes.
First you would create a class to represent each TextBox
public class MyClass
{
public string Text { get; set; }
public int X { get; set; }
public int Y { get; set; }
}
Note: This class should implement INotifyPropertyChanged if you want to change the properties at runtime and have the UI automatically update.
Then make a list of this class, and bind it to an ItemsControl
<ItemsControl ItemsSource="{Binding ListOfMyClass}" />
Then you'd want to overwrite the ItemsPanelTemplate to be a Canvas (the best WPF panel for positioning items according to an X,Y position)
<ItemsControl ItemsSource="{Binding ListOfMyClass}">
<!-- ItemsPanelTemplate -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
Next overwrite the ItemTemplate to draw each item using a TextBlock
<!-- ItemTemplate -->
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Text}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
And add an ItemContainerStyle that binds Canvas.Left and Canvas.Top properties to X,Y properties on your object
<!-- ItemContainerStyle -->
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Left" Value="{Binding X}" />
<Setter Property="Canvas.Top" Value="{Binding Y}" />
</Style>
</ItemsControl.ItemContainerStyle>
And this will take a List of MyClass objects, and render them to the screen inside a Canvas, with each item positioned at the specified X,Y coordinates.
With all that being said, are you sure this is what you want? WPF has much better layout panels than WinForms, and you don't have to position every element according to an X,Y coordinate if you don't want to.
For a quick visual introduction of WPF's Layouts, I'd recommend this link : WPF Layouts - A Visual Quick Start
Also since it sounds like you're new to WPF and coming from a WinForms background, you may find this answer to a related question useful : Transitioning from Windows Forms to WPF
WPF layout involves choosing a layout container and placing your controls in it. There are several different containers:
The Grid container is a powerful tool for laying out your form in rows and columns. You have complete control over the size of each cell, and you can have rows or columns "span" each other.
The DockPanel container allows you to "dock" controls to the edges of your window or the center. You'd use it to layout a window with smart icon bars, ribbons, status windows, and toolboxes, like Visual Studio itself.
The StackPanel container can be used to stack controls either on top of each other or next to each other
The UniformGrid container is a less powerful version of the container that keeps all cells the same size.
The Canvas container allows you to specify the X & Y coordinates of your controls.
There are one or two others but these are the ones I've used.
The bad thing about laying out a form using X & Y coordinates is that the form does not handle resizing well. This can be exacerbated when you support globalization, as the labels and such for a string may be a lot longer in a foreign language. The best example off the top of my head is Spanish. A lot of English phrases, when translated to Spanish, are a lot longer.
The Grid container gives you the most control over layout. Columns can automatically size themselves to the longest string in the column, while the rest of the columns adjust themselves as necessary, again automatically. You don't have to write one line of code to get that effect; it's all there in the Grid control out of the box.
If you insist on laying out your form the Winforms way, use a Canvas. But you're not going to get the benefit of using the more advanced layout facilities in the other containers, especially the Grid control. I use that almost exclusively in my forms.
EDIT
Using layout controls other than Canvas means that you think about layout differently in WPF than in WinForms. You work at a higher conceptual level and leave the details about figuring out where on the screen a particular control will be displayed to WPF. You also don't have things like the WinForms Anchor property in WPF, which always seemed kind of a hack to me.
The WPF was designed to offer a power and rich framework for designer which make it a different from the classic winforms. You can achieve what want by adding your TextBox control to a canvas and changing the attached property following is a full example illustrating this:
MainWindow
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Canvas Name="mainCanvas" Margin="31,-10,-31,10">
<TextBox Name="myTextBox" Canvas.Left="131" Canvas.Top="109" Height="84" Width="135"></TextBox>
<Button Content="Button" Height="62" Canvas.Left="271" Canvas.Top="69" Width="91" Click="Button_Click"/>
</Canvas>
</Grid>
</Window>
Code Behind
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
myTextBox.SetValue(Canvas.LeftProperty,(double)myTextBox.GetValue(Canvas.LeftProperty)+50.0);
}
}
}
If you want to position the TextBoxes in a grid-way, use Grid:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
...
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Grid.Column="0" />
<TextBox Grid.Row="0" Grid.Column="1" />
<TextBox Grid.Row="0" Grid.Column="2" />
...
<TextBox Grid.Row="1" Grid.Column="0" />
<TextBox Grid.Row="1" Grid.Column="1" />
<TextBox Grid.Row="1" Grid.Column="2" />
...
</Grid>

Find child elements in a WPF ListView ItemTemplate

Given a populated ListView, how do I iterate through each bound template and pluck out the contained ComboBox (or any other control contained in DataTemplate)?
<ListView x:Name="lstCommands">
<ListView.ItemTemplate>
<DataTemplate>
<Grid x:Name="gridInputs">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Content="{Binding Path=Key}"/>
<ComboBox x:Name="cbInputCmd" Grid.Column="1" ItemsSource="{Binding Source={StaticResource inputData}}" Tag="{Binding Path=Key}"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
Firstly, avoid doing so unless you really need to. If you absolutely must, you can use DataTemplate.FindName, where the templated parent is the ListViewItem generated by the ListView. To get the ListViewItem, use the ListView's ItemContainerGenerator.
Update: the reason I suggest avoiding this approach wherever possible is because it creates more tightly-coupled, brittle code. The OP didn't mention why he wanted to do what he was asking, but I suspect he could achieve his goal by more idiomatic means, such as with bindings.
is simple
just do this
<ListViewItem Name="mainiterm" Style="{ StaticResource inboxlst}" Selected="ListViewItem_Selected_1">
<Canvas Style="{StaticResource inboxcanvas}">
<Label Name="namelabel" Content="lalallala1" Style="{StaticResource inboxlabel1}" />
<Label Content="lalallala" Style="{StaticResource inboxlabel2}"/>
</Canvas>
</ListViewItem>
and vb
Dim r = mlistview.Items.GetItemAt(i)
Dim textYear As Label = Nothing
Dim s As Canvas = r.Content
Dim a As Label = s.Children.Item(1)
a.Content = "Disconnected"
a is a label, s is a cavas
you could try using the LogicalTreeHelper or VisualTreeHelper which lets you query an object for its children, but if you were binding your combo boxes to the item your list view is displaying you would not have to worry about 'getting' them at all.
Then you could just look at your item.
Any time you find yourself walking the visual or logical tree looking for elements which exist in your ui, so that you can get their values, ask yourself 'what am i missing here' 'why isnt my business (or view model) being updated with relevant data when the user interacts with the ui?'
For the example above I would build a view model that had two properties, a String (for your label) and a SelectedItem (that you could bind your combo box selected item to). its easier, more robust and it stops you having to trawl through the visual elements. one of the beautiful things about xaml/wpf is that it seperates your logic from your view. what you are suggesting will break this model. You will entangle the view with your logic and from there on it gets messy...

Multi-Column Selector

I am searching for some hybrid of ComboBox and ListView and I am wondering why there exists nothing like that, although I feel it's a quite natural wish to have it.
In more detail:
A WPF ItemsControl should have an ItemsSource of all applicable items.
These items have multiple properties, say ID:int, Name:string and Description:string.
Now my ItemControl should:
Show these three properties as nicely aligned columns in some combobox-like drop-down
Provide some way of quickly finding an item by just typing text into a single textbox (without specifying, which property shall be searched). This should either select the first match or filter the items hiding all non-matching ones.
Key is that the control is perfectly usable without a mouse, but also provides some "explorer"-mode, if the user does not remember the perfectly identifying ID but only parts of some description or name.
A configurable "Search-Function" would be nice, and it would be no problem if you would need to explicitly state all the properties to be included in a search or display function.
There is no such control so far, but you can certainly make one easily,
Create a C# Custom Control and define control template as Expander containing a DataGrid/ListView.
You can define dependency properties of your custom control as needed for ListView and do templatebinding for them. Expander's header should be bound to selected item's text or some sort of text representation of your objects.
Alternative:
Add a GRID inside Item Template and define its column definitions. And you can specify multi column values in the GRID easily.
<ComboBox>
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding CustomerName}"/>
<TextBlock Grid.Column="1" Text="{Binding CustomerEmail}"/>
<TextBlock Grid.Column="2" Text="{Binding CustomerPhone}"/>
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
By applying widths correctly, and giving margins to textblock, you can create multicolumn list to be displayed easily. Dont forget to TextSearch.SearchPath property in order to be able to select item by keyboad.

Categories