WPF Design Time Data and Data Templates - c#

I'm developing a WPF user control and I want it to display some design-time data. It's proving to be quite a challenge to get things just right and I could really use your help.
Here's my control so far, basically a <ListBox>:
<UserControl x:Class="TA.Weather.UI.Wpf.WorkingSetEditor"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Loaded="HandleLoadedEvent"
>
<Grid d:DataContext="{d:DesignData Source=/SampleData/WorkingSet.xaml}">
<ListBox x:Name="WorkingSetListBox" >
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="30" />
<ColumnDefinition MinWidth="30" />
<ColumnDefinition MinWidth="30" />
<ColumnDefinition MinWidth="30" />
<ColumnDefinition MinWidth="30" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" TextAlignment="Center"
Text="{Binding Path=[0].Limits.StationId}"></TextBlock>
<TextBlock Grid.Column="1" TextAlignment="Center"
Text="{Binding Path=[0].Limits.SensorId}"></TextBlock>
<TextBlock Grid.Column="2" TextAlignment="Center"
Text="{Binding Path=[0].Limits.SensorName}"></TextBlock>
<TextBlock Grid.Column="3" TextAlignment="Center"
Text="{Binding Path=[0].Limits.LowerLimit}"></TextBlock>
<TextBlock Grid.Column="4" TextAlignment="Center"
Text="{Binding Path=[0].Limits.UpperLimit}"></TextBlock>
<CheckBox Grid.Column="5" x:Name="MonitoredCheckBox"
IsChecked="{Binding Path=[1].Monitored}"/>
</Grid>
</ListBox>
</Grid>
</UserControl>
I figured out how to create the design-time data in another XAML file, and set the build action to DesignDataWithDesignTimeCreatableTypes. The type is essentially just a DTO so it seems OK to create instances at design time. So here's the data file, containing an List<SensorState> with two members:
<generic:List x:TypeArguments="ws:SensorState"
xmlns:ws="clr-namespace:TA.Weather.Core;assembly=TA.Weather.Core"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:generic="clr-namespace:System.Collections.Generic;assembly=mscorlib">
<ws:SensorState IdleTimeout="300" LastUpdate="2015-08-19T03:39:00.000Z" Monitored="False"
WithinAcceptableLimits="False">
<ws:SensorState.Limits>
<ws:SensorLimits StationId="12-34-56-78-90-12" SensorId="1" SensorName="Inside Temperature"
LowerLimit="-10" UpperLimit="30" />
</ws:SensorState.Limits>
</ws:SensorState>
<ws:SensorState IdleTimeout="300" LastUpdate="2015-08-19T03:39:00.000Z" Monitored="True"
WithinAcceptableLimits="False">
<ws:SensorState.Limits>
<ws:SensorLimits StationId="12-34-56-78-90-12" SensorId="1" SensorName="Inside Temperature"
LowerLimit="-10" UpperLimit="30" />
</ws:SensorState.Limits>
</ws:SensorState>
</generic:List>
Thus far, this gets me a user control that looks like this:
So, success of sorts. However, there are two questions at this point:
Why does only one row show up when there are two items in the list? I can control which instance shows up by changing the array index in the binding expression, but how do I get the rows to repeat? I tried removing the array index but that just gives a compile error.
I'm getting errors against the data file of the form "The member XXX is not recognized or is not accessible", one for each property that I'm setting, 36 in total. Even though the build succeeds and the design-time data shows up in the editor. They are errors so I'm sure they are important - I just have no idea what the compiler is trying to tell me. So what do these errors mean and what do I need to do so mitigate them?
There's more. I want to use a DataTemplate to create a nicer look for the data; a bit later on there is going to be a slider control for setting the limits, etc. so here's how I've tried to use a template:
<UserControl x:Class="TA.Weather.UI.Wpf.WorkingSetEditor"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:core="clr-namespace:TA.Weather.Core;assembly=TA.Weather.Core"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Loaded="HandleLoadedEvent"
>
<Grid d:DataContext="{d:DesignData Source=/SampleData/WorkingSet.xaml}">
<Grid.Resources>
<DataTemplate x:Key="WorkingSetTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="30" />
<ColumnDefinition MinWidth="30" />
<ColumnDefinition MinWidth="30" />
<ColumnDefinition MinWidth="30" />
<ColumnDefinition MinWidth="30" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" TextAlignment="Center"
Text="{Binding Path=[0].Limits.StationId}"></TextBlock>
<TextBlock Grid.Column="1" TextAlignment="Center"
Text="{Binding Path=[0].Limits.SensorId}"></TextBlock>
<TextBlock Grid.Column="2" TextAlignment="Center"
Text="{Binding Path=[0].Limits.SensorName}"></TextBlock>
<TextBlock Grid.Column="3" TextAlignment="Center"
Text="{Binding Path=[0].Limits.LowerLimit}"></TextBlock>
<TextBlock Grid.Column="4" TextAlignment="Center"
Text="{Binding Path=[0].Limits.UpperLimit}"></TextBlock>
<CheckBox Grid.Column="5" x:Name="MonitoredCheckBox"
IsChecked="{Binding Path=[1].Monitored}"/>
</Grid>
</DataTemplate>
</Grid.Resources>
<ListBox x:Name="WorkingSetListBox" ItemTemplate="{DynamicResource WorkingSetTemplate}">
</ListBox>
</Grid>
</UserControl>
So basically, I've moved the <Grid> out of the <ListBox> and into the <DataTemplate> resource. As soon as I do this, the design-time data vanishes and I'm left with just a blank control in the designer. So my third question becomes:
Why does the design-time data vanish when I use a <DataTemplate>? What do I need to do differently?
You can probably tell that I have almost no experience with WPF... I must admit I am struggling with binding expressions a little bit, they seem like some sort of arcane magic with little rhyme or reason to them. I think maybe I'm missing an important concept somewhere. I hope you can give me a few wise words to push me in the right direction.

Related

WPF Listbox stretching issues

I have created a UserControl that is suppose to display some text and then present a listbox for the user to select some item.
It is supposed to strech to the height to the grid cell it is placed in. And if there are more items than can fit on screen then to display a vertical scrollbar. After much trial and error I have finally managed to get it working as expected. But only when the row height is a pixel value. If I use any partial (by partial I mean the star symbol), the grid cell's height is not contained. Since I need the app the match screen size I need the partials to work.
Putting 100 items in the listbox suddenly makes the height of the entire grid is around 11000 (which I think is everything with no contraint). Meaning there is no scrollbar. See [Screenshot] https://i.stack.imgur.com/faES9.png.
I have a similar issue with my TextBox only horizontally. It will only activate when clicking on a specific pixel since it is empty the width is only 1 pixel. I hope someone can help me point out where I am going wrong, cause since I'm having more than one issue I suspect the problem is on my end.
This is my XAML code.
<UserControl x:Class="PackingStation.UserControls.SearchItemListing"
x:Name="SearchItemListingRoot"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:PackingStation.UserControls"
mc:Ignorable="d"
d:DesignHeight="1000" d:DesignWidth="1000">
<Grid DataContext="{Binding ElementName=SearchItemListingRoot}"
x:Name="TheGrid"
VerticalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height=".1*" />
<RowDefinition Height=".1*" />
<!-- If this value isnt a pixel value it will not vertically strech to available space -->
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel HorizontalAlignment="Center"
Margin="0,0,0,0"
x:Name="TheHeadLineField"
Grid.Row="0"
Grid.Column="1">
<Label Style="{StaticResource LabelStyleHeadline32}"
Content="{Binding Title}"
HorizontalAlignment="Left"/>
<Label Style="{StaticResource LabelStyleText}"
HorizontalAlignment="Left"
Content="{Binding SubTitle}"
FontSize="20"/>
</StackPanel>
<!-- Search Field -->
<Border Style="{StaticResource Border6}"
x:Name="TheSearchField"
Grid.Row="1"
Grid.Column="1"
Background="{StaticResource ColorSummaryPanelBackground}"
BorderBrush="{StaticResource ColorSummaryPanelBorder}"
BorderThickness="1"
Margin="0,40,0,0">
<StackPanel Orientation="Horizontal"
Height="78"
HorizontalAlignment="Stretch">
<Image Stretch="Uniform"
Height="24"
Source="pack://application:,,,/WpfUI;component/Icons/Search.png"
Margin="36,0,0,0"
VerticalAlignment="Center"/>
<!-- Search Textbox -->
<!-- Similar issue with this textbox, only horizontally. With no content you have to hit a specific pixel to activate it -->
<TextBox Style="{StaticResource TextBoxSearchInlay}"
Name="TbxSearchTerm"
TextChanged="TextBoxBase_OnTextChanged"
HorizontalContentAlignment="Stretch"
HorizontalAlignment="Stretch"
Margin="20,0,0,0"/>
</StackPanel>
</Border>
<!-- Search results listings -->
<ListBox ItemsSource="{Binding Items}"
x:Name="TheListBox"
Grid.Row="2"
Grid.Column="1"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
ScrollViewer.VerticalScrollBarVisibility="Auto"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel HorizontalAlignment="Stretch" Margin="0,-10,0,-10">
<Label Style="{StaticResource LabelSearchItem}"
Content="{Binding Name}"
HorizontalContentAlignment="Stretch"
HorizontalAlignment="Stretch"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</UserControl>
I could repro your problem when I put your UserControl in a StackPanel. When it's in a Grid the ListBox shows its vertical scrollbar. So, try your UserControl in a Grid.
I think this answer will give a good explanation about Grid and StackPanel stretching behavior.

UserControl in DataTemplate does not bind correctly

I've been trying to create a custom menu. For this reason I wanted to use an ItemsControl in order to make it flexible. After hours of headache I figured out how to make it - kinda.
I have my custom ItemsControl "LiftMenu" (which is not yet much custom but standard) and an UserControl called "LiftItem". Last but not least I got the Model-class "LiftMenuItem".
By adding a new LiftMenuItem to the LiftMenu, it should display a new LiftItem-control as corresponding item. So far so good, I managed to get this working.
In that LiftItem-control I bind like I would in a normal DataTemplate: plain bindings with a path, nothing more. Normally this would work just fine because the DataTemplate already has it's context set to the model-type.
But now I just get an empty control that does nothing and shows nothing, because the bindings don't work.
I implemented it this way:
<menu:LiftMenu HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MinHeight="200" Background="#80A8A8A8" Margin="5,0,0,0">
<menu:LiftMenu.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</menu:LiftMenu.ItemsPanel>
<menu:LiftMenu.ItemTemplate>
<DataTemplate DataType="{x:Type menu:LiftMenuItem}">
<menu:LiftItem />
</DataTemplate>
</menu:LiftMenu.ItemTemplate>
<menu:LiftMenuItem Header="Test1"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="4"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Border x:Name="border" BorderBrush="{Binding LabelColor}" BorderThickness="1" HorizontalAlignment="Left" Height="Auto" Margin="0"
VerticalAlignment="Stretch" Width="4" Background="{Binding BorderBrush, ElementName=border}"/>
<TextBlock Text="{Binding Header}" TextTrimming="CharacterEllipsis" Margin="5,0,5,0" HorizontalAlignment="Left" VerticalAlignment="Center"
Foreground="White" />
<controls:ProgressRing x:Name="ring" Grid.Column="2" HorizontalAlignment="Right" Stroke="#ffff8000" VerticalAlignment="Center"
Minimum="0" Maximum="100" Value="{Binding ProcessValue}" IsIndeterminate="{Binding ProcessIndeterminate}" Visibility="{Binding ProcessVisibility}"
Width="20" Height="20" Radius="10" Margin="2,0,2,0" />
</Grid>
In the end there is no text, no border. Just the ProgressRing is visible.
How can I fix this? This ListItem-control should become similiar to a button, thus I need to do some styling (animation, ...). I can't do this within a normal DataTemplate, but I don't want to miss the binding features of WPF on that. This would make it relatively unflexible.
What's the problem? I probably just miss some DataContext or so, but I don't know what it would be.

WPF MVVM with data templates for base/derived classes

I want to do something like that:
I have a base class VerticalConnectorViewModel and a derived class StairsViewModel.
I use WPF data templates to define a view for those objects.
For example my base class is assigned like this (inside the UserControl.Resources:
<!-- Define a data-template for the 'VerticalConnectorViewModel' class. This generates the UI for each node. -->
<DataTemplate DataType="{x:Type model:VerticalConnectorViewModel}">
<network:VerticalConnectorView></network:VerticalConnectorView>
</DataTemplate>
<!-- Define a data-template for the 'StairsViewModel' class. This generates the UI for each node. -->
<DataTemplate DataType="{x:Type model:StairsViewModel}">
<network:StairsView></network:StairsView>
</DataTemplate>
I have created two different Usercontrols, namely the 'network:VerticalConnectorView' and the 'network:StairsView'. Both look similar, except for some additional elements for the latter.
My VerticalConnectorView.xaml file looks like this (but is not really important here I think):
<UserControl x:Class="NetworkUI.VerticalConnectorView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:network="clr-namespace:NetworkUI"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid IsSharedSizeScope="True">
<StackPanel>
<network:NodeView x:Name="nodeView"></network:NodeView>
<Border Grid.Row="0" BorderBrush="Black" BorderThickness="1" Margin="0" SnapsToDevicePixels="True">
<StackPanel Width="Auto" HorizontalAlignment="Stretch" >
<Grid Width="Auto" Height="Auto">
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="sharedDescription" Width="Auto"></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<network:EditableTextBlock Grid.Column="0" Text="Type" FontSize="10" IsEditable="False"></network:EditableTextBlock>
<ComboBox Grid.Column="1" HorizontalAlignment="Stretch" SelectedItem="{Binding Type}" ItemsSource="{Binding TypeValues}"></ComboBox>
</Grid>
</StackPanel>
</Border>
<Border Grid.Row="0" BorderBrush="Black" BorderThickness="1" Margin="0" SnapsToDevicePixels="True">
<StackPanel Width="Auto" HorizontalAlignment="Stretch" >
<Grid Width="Auto" Height="Auto">
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="sharedDescription" Width="Auto"></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<network:EditableTextBlock Grid.Column="0" Text="Placement" FontSize="10" IsEditable="False"></network:EditableTextBlock>
<ComboBox Grid.Column="1" HorizontalAlignment="Stretch" SelectedItem="{Binding Placement}" ItemsSource="{Binding PlacementValues}"></ComboBox>
</Grid>
</StackPanel>
</Border>
</StackPanel>
</Grid>
</UserControl>
There are two ComboBox elements. The first one lets you select 'Random', 'Stairs', 'Elevator or 'Escalator'. Now, what I want to do, is if one selects 'Stairs' I want to change my ViewModel from VerticalConnectionViewModel to StairsViewModel (and let the WPF use the other View).
Since I use a Collection for all my ViewModels I already tried to use the SelectionChanged event for the removal of the old ViewModel and the addition of the new one (instance of a different class), but that didn't work either.
Is this possible or should I rethink my approach? I know I can e.g. use only one class and update the UI for different selected values, but should I really add all possible values to my base-class? E.g. it makes no sense to have a StairsType property if the type of the VerticalConnector is set to 'Elevator', right?

How to make controls fill the container in WPF?

Below is my xaml code :
<Window x:Class="ScoresBank.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow">
<DockPanel LastChildFill="True">
<WrapPanel DockPanel.Dock="Top">
<ToolBar Height="25"></ToolBar>
</WrapPanel>
<WrapPanel DockPanel.Dock="Bottom">
<StatusBar Name="stbBottom" Height="25"
BorderThickness="2"
BorderBrush="Black">Example</StatusBar>
</WrapPanel>
<StackPanel DockPanel.Dock="Left">
<DockPanel LastChildFill="True">
<WrapPanel DockPanel.Dock="Top">
<Button Name="btnBrowseTags">TAGS</Button>
<Button Name="btnBrowseTitles">TITLES</Button>
</WrapPanel>
<ComboBox DockPanel.Dock="Top" Name="cbxCategory">
Example
</ComboBox>
<WrapPanel DockPanel.Dock="Bottom">
<TextBox Name="txtKeyword"></TextBox>
<Button Name="btnFind">Find</Button>
</WrapPanel>
<ListBox Name="lbxBrowse">
</ListBox>
</DockPanel>
</StackPanel>
<StackPanel DockPanel.Dock="Right">
<Button>Example</Button>
</StackPanel>
<Canvas>
</Canvas>
</DockPanel>
And this is my current layout view :
So, what I mean with filling the container are :
Making lbxBrowse to fill the mid-space of the DockPanel inside the left StackPanel.
Making txtKeyword to fill the WrapPanel.
Making stbBottom to fill the WrapPanel.
What I've tried :
It seems i've put them in a DockPanel with LastChildFill="True". But it seems doesn't work.
I don't know about this, since it's not possible to put it into a DockPanel first.
I don't know anything about this.
I don't use fixed size, since I need them to keep neat even when resized in multiple screen resolution. The fixed size on ToolBar and StatusBar seems required, otherwise, the text will be unseen.
P.S. If possible, I prefer the solution to be XAML code, rather than the C# code. Otherwise, C# code is fine too.
Thank you.
You should use a Grid. It is more easier to configure. Here is an example (I don't know exactly how you want to setup your layout).
<Window x:Class="SampleWpf.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="400" Width="600">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Margin="5" Content="TAGS"
Grid.Column="0" Grid.Row="0" />
<Button Margin="5" Content="TITLES"
Grid.Column="1" Grid.Row="0" />
<Button Margin="5" Content="EXAMPLES"
Grid.Column="2" Grid.Row="0"
HorizontalAlignment="Right"/>
<ComboBox Margin="5"
Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="1" />
<ListBox Margin="5"
Grid.Column="2" Grid.Row="1" Grid.RowSpan="2" />
<Button Margin="5" Content="EXAMPLE"
Grid.Column="2" Grid.Row="3" HorizontalAlignment="Left" />
</Grid>
</Window>
And the result:
With a grid you can set the height and the width of the columns and rows to a specific value (in points, pixels, cm, etc.), to column content (Auto) or proportional to the grid (with *).
Instead of using a StackPanel and DockPanel you can use Grid and set Grid.ColumnDefinitions and Grid.RowDefinitions. You can specify directly each row Height and each column Width. It's easier to use and it automaticly fit to content and container.

WPF ListBox SelectionBox

I have a DataTemplate for my WPF ListBox:
<DataTemplate DataType="{x:Type local:LogEntry}" x:Key="lineNumberTemplate">
<Grid IsSharedSizeScope="True">
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="Index" Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid Cursor="/LogViewer;component/Template/RightArrow.cur">
<Rectangle Fill="{Binding Path=LineNumbersBackgroundColor, ElementName=LogViewerProperty}" Opacity="0.4" />
<TextBlock Grid.Column="0" Margin="5,0,5,0" Style="{StaticResource MyLineNumberText}" x:Name="txtBoxLineNumbers" />
</Grid>
<TextBlock Grid.Column="1" Margin="5,0,0,0" Style="{StaticResource MyTextEditor}" />
</Grid>
</DataTemplate>
Is it possible that the selection box begins not at the beginning (MyLineNumberText) but at MyTextEditor? Sorry I don't know how to describe it in the right way.
Yes, it is possible. You have to modify the style of the listbox. If you are using Blend this is easy. Otherwise you could get the style for Listbox and ListboxIten here: http://msdn.microsoft.com/en-us/library/cc278062(v=vs.95).aspx
Copy the style to your project and then change the style acordingly.

Categories