Dynamically add child expander - wpf

Is there a way to create child expanders like this xaml bellow, at run-time?
<Expander Header="Building" IsExpanded="True">
<StackPanel>
<Expander Header="Sales">
<StackPanel>
<TextBlock>6100</TextBlock>
<TextBlock>6101</TextBlock>
<TextBlock>6111</TextBlock>
<TextBlock>6150</TextBlock>
</StackPanel>
</Expander>
<Expander Header="Director">
<StackPanel>
<TextBlock>6102</TextBlock>
<TextBlock>6113</TextBlock>
</StackPanel>
</Expander>
</StackPanel>
</Expander>

Set a name for first StackPanel (MainStackPanel) under the top Expander.
// Add new expander, stack panel and text block.
var newExpander = new Expander {Name = "NewExpander", Header = "New Expander"};
var newstackPanel = new StackPanel {Name = "NewExpanderStackPanel"};
var newtextBlock = new TextBlock {Text = "777"};
// Add above items as children.
newstackPanel.Children.Add(newtextBlock);
newExpander.Content = newstackPanel;
MainStackPanel.Children.Add(newExpander);

The best way would probably be to use data binding. I'm assuming you have some data structure that describes which expanders you want to create. Let's assume we have:
class Building
{
public List<Item> Items { get; }
public string Name { get; }
}
class Item
{
public int Number { get; }
}
In our user control, we'll set the DataContext to a list of Buildings (e.g. in the constructor):
DataContext = GetListOfBuildings();
Then we'd use two nested ItemsControls with templates to create the controls at runtime:
<Expander Header="Building"
IsExpanded="True">
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Expander Header="{Binding Name}">
<ItemsControl ItemsSource="{Binding Items}"
DisplayMemberPath="Number" />
</Expander>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Expander>
This way you would not need to create controls in C#, which is more cumbersome in WPF.

Not quite sure about what you want to achieve.
If I understand correctly, you can save your code in a .xaml file as resource and use application to load it. Then, add the object to the visual tree.
var expander = (Expander) Application.LoadComponent(new Uri("UriToTheXamlFile"));
control.AddChild(expander);

Related

Buttons in StackPanel bound to List

I'm trying to create a StackPanel which will contain Buttons. Each Button will contain a string property from an object from a List kept in the ViewModel. After clicking a button a popup control should display. I'm having problems with the StackPanel - it doesn't display my items from the list. What did I do wrong?
<ItemsControl ItemsSource="{Binding ItemsList}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Path=ItemName}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Set the DataContext of the ItemsControl to an instance of the class where the ItemsList property is defined and make sure that there are some items in the collection returned by the ItemsList property:
public MainWindow()
{
InitializeComponent();
DataContext = new YourViewModel();
}
Then it should work provided that ItemsList and ItemName are actually public properties and not fields.
public List<ContactName> ItemsList { get; } = new List<ContactName>() { new ContactName { Name = "Name01" }, new ContactName { Name = "Name02" }, new ContactName { Name = "Name03" } };
So the solution was that the code was fine but the Button didn't adjust its size according to the Content and I just couldn't see it. After manually hard-coding the Width and Height and Padding set to 0 the contents were finally visible.
<Button Content="{Binding Path=ItemName}" Width="100" Height="30" Padding="0"/>

How to choose View dynamically based on current DataContext view model

I have a Page which will receive a different DataContext (View Model), dynamically.
I can't figure out how to use DataTemplate in a switch/case fashion, to render the appropriate view based on the current context.
I would imagine that I will have multiple DataTemplates like this:
<DataTemplate DataType="{x:Type LocalViewModels:ABC}">
<LocalViews:ABC/>
</DataTemplate>
but can't figure out in what container to put them. Only one of them will be rendered at a time, so ListBox makes no sense to me.
Given the following XAML of a Window
<Window.Resources>
<DataTemplate DataType="{x:Type local:ABC}">
<Border BorderThickness="2" BorderBrush="Red">
<TextBlock Text="{Binding Text}"/>
</Border>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ContentControl Content="{Binding}"/>
</StackPanel>
you can simply assign an instance of ABC to the Window's DataContext to create the templated view.
class ABC
{
public string Text { get; set; }
}
...
public MainWindow()
{
InitializeComponent();
DataContext = new ABC { Text = "Hello, World." };
}
All details are here: Data Templating Overview.

Using different panels for different sizes of items in WPF

I want to use different panels for my container's item count. Is there a way that I can achieve this?
For example, I want to use PanelA for less than 5 items, PanelB for more.
I implemented the DataTemplateSelector class for this purpose and it seems to work. But when it comes to draw the UI element, I get no results but the blank white screen.
public class PanelDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
FrameworkElement element = container as FrameworkElement;
MyViewModel VM = (VisualTreeHelper.GetParent(container) as ContentControl).DataContext as MyViewModel;
DataTemplate template = null;
if (VM.ItemsList.Count < 5)
template = element.FindResource("PanelA") as DataTemplate;
else
template = element.FindResource("PanelB") as DataTemplate;
return template;
}
}
Following is the one of the templates in my XAML code. Second one is just the same but PanelB.
<DataTemplate x:Key="PanelA">
<ListBox x:Name="ItemsListBox" ItemsSource="{Binding Path=ItemsList}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Image Source="{Binding Path=Image}"/>
<TextBlock Text="{Binding Path=Label}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<mynamespace:PanelA />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</DataTemplate>
<DataTemplate x:Key="PanelB">
...
</DataTemplate>
After resources, I have the following ContentControl section.
<Grid>
<ContentControl>
<ContentControl.ContentTemplateSelector>
<local:PanelDataTemplateSelector />
</ContentControl.ContentTemplateSelector>
</ContentControl>
</Grid>
Thanks

Creating and binding buttons dynamically in a WrapPanel

In this scenario, an array of resources are sent to the ViewModel.
The objective is to display these resources as buttons in a WrapPanel in the view.
At this moment, I'm doing this using the C# code below. However, I'd like to do this in the Xaml side. Eventually, I'd like to use DataTemplates to format the buttons with other properties of the Resource class.
What is the best way of approaching this? Thanks in advance.
public void SetResources(Resource[] resources)
{
WrapPanel panel = this.View.ResourcesPanel;
panel.Children.Clear();
foreach(Resource resource in resources)
{
var button = new Button
{
Tag = resource.Id,
Content = resource.Content,
Width = 300,
Height = 50
};
button.Click += this.OnResourceButtonClick;
panel.Children.Add(button);
}
}
A common way to display a variable set of items would be to use an ItemsControl. You would bind the ItemsControl's ItemsSource property to an ObservableCollection of your Resource objects.
<UserControl ...>
<UserControl.DataContext>
<local:ViewModel/>
</UserControl.DataContext>
<Grid>
<ItemsControl ItemsSource="{Binding Resources}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Tag="{Binding Id}" Content="{Binding Content}"
Width="300" Height="50"
Click="OnResourceButtonClick"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
The ViewModel class might look like this:
public class ViewModel
{
public ViewModel()
{
Resources = new ObservableCollection<Resource>();
}
public ObservableCollection<Resource> Resources { get; private set; }
}
You may access the ViewModel instance in the UserControl or MainPage code by casting the DataContext:
var vm = DataContext as ViewModel;
vm.Resources.Add(new Resource { Id = 1, Content = "Resource 1" });
...

wpf Usercontrol template

In my MVVM app I have a treeview representing records in a database. My views and viewmodels are linked in a resource dictionary like this
<DataTemplate DataType="{x:Type vm:TrialSiteViewModel}">
<vw:TrialSiteView />
</DataTemplate>
I want to display a preview of the view when a user hovers over an icon using the tooltip. My HierarchicalDataTemplate in the treeview is this
<HierarchicalDataTemplate DataType="{x:Type vm:TrialSiteViewModel}"
ItemsSource="{Binding Path=Children}">
...
<Button Style="{StaticResource previewButtonStyle}">
<Button.ToolTip>
<ToolTip Style="{x:Null}">
<ToolTip.ContentTemplate>
<DataTemplate>
<localtools:ObjectPreview
PreviewObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TreeViewItem}}, Path=DataContext}"
/>
</DataTemplate>
</ToolTip.ContentTemplate>
</ToolTip>
</Button.ToolTip>
</Button>
</StackPanel>
</HierarchicalDataTemplate>
This correctly picks up the TrialSiteViewModel that is the DataContext for the Treeviewitem.
ObjectPreview uses a viewbox and contentcontrol to display the view of the record
<Viewbox Grid.Row="1" Name="treeviewViewBox"
Stretch="Uniform"
IsEnabled="False">
<ContentControl Name="treeViewItemViewModel"
Content="{Binding PreviewObject}">
</ContentControl>
</Viewbox>
and the code behind contains the dependency property
public partial class ObjectPreview : UserControl
{
public ObjectPreview()
{
InitializeComponent();
}
public static readonly DependencyProperty _previewObjectProperty =
DependencyProperty.Register("PreviewObject", typeof(TreeViewItemViewModel), typeof(ObjectPreview));
public TreeViewItemViewModel PreviewObject
{
get { return (TreeViewItemViewModel)GetValue(_previewObjectProperty); }
set { SetValue(_previewObjectProperty, value); }
}
}
The problem I'm having is that the Template used to display the object is the same as that used in the treeview. This simply shows an icon and an object summary (ie. Primary Key and one or two key fields) rather than the entire template as defined in the view TrialSiteView. If I amend the code to use a button Command on the TrialSiteViewModel and inject it into ObjectPreview I can set the contentcontrol in the code behind and the TrialSiteView is used.
I'm guessing that somehow the Template is inferred from the TreeViewItem. Can anyone tell me how I can ensure the tooltip uses the TrialSiteView?
UPDATE
Ok, so I've fixed this but had to resort to code behind and removed the usercontrol and put the view directly in the tooltip. The key bit is getting the datatemplate from the resources. I'd tried to do this previously by assigning a key to the datatemplate, but either my code was flawed or it did not work. Anyhow, this works but is not the preferred Xaml solution.
private void PreviewObject_MouseEnter(object sender, MouseEventArgs e)
{
Image image = (Image)sender;
var key = new System.Windows.DataTemplateKey(image.DataContext.GetType());
var datatemplate = (DataTemplate)this.FindResource(key);
ToolTip tooltip = new ToolTip();
tooltip.Style = VisualUtils.GetResource<Style>("ControlTemplates.xaml", "toolTipWithContentStyle");
tooltip.MaxWidth = 460;
ContentControl contentcontrol = new ContentControl();
contentcontrol.ContentTemplate = datatemplate;
contentcontrol.Content = image.DataContext as TreeViewItemViewModel;
Viewbox viewbox = new Viewbox();
viewbox.Stretch = Stretch.Uniform;
viewbox.Child = contentcontrol;
tooltip.Content = viewbox;
image.ToolTip = tooltip;
}
What you need to do is to specify explicitly what data template to use. In order to do that just add a template property along with the PreviewObject property in the preview control:
public static readonly DependencyProperty _previewObjectTemplateProperty =
DependencyProperty.Register("PreviewObjectTemplate", typeof(DataTemplate), typeof(ObjectPreview));
public DataTemplate PreviewObjectTemplate
{
get { return (DataTemplate)GetValue(_previewObjectTemplateProperty); }
set { SetValue(_previewObjectTemplateProperty, value); }
}
Then, in the ObjectPreview.xaml add the ContentTemplate property that is bound to the PreviewObjectTemplate property:
<Viewbox Grid.Row="1" Name="treeviewViewBox"
Stretch="Uniform"
IsEnabled="False">
<ContentControl Name="treeViewItemViewModel"
Content="{Binding PreviewObject}"
ContentTemplate="{Binding PreviewObjectTemplate}" >
</ContentControl>
</Viewbox>
And finally, give a key to your data template and specify a reference to it explicitly when you declare ObjectPreview:
<DataTemplate x:Key="FullViewTemplate" DataType="{x:Type vm:TrialSiteViewModel}">
<vw:TrialSiteView />
</DataTemplate>
...
<ToolTip Style="{x:Null}">
<ToolTip.ContentTemplate>
<DataTemplate>
<localtools:ObjectPreview
PreviewObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TreeViewItem}}, Path=DataContext}"
PreviewObjectTemplate="{StaticResource FullViewTemplate}"
/>
</DataTemplate>
</ToolTip.ContentTemplate>

Resources