Creating and binding buttons dynamically in a WrapPanel - silverlight

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" });
...

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.

Dynamically add child expander

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);

WPF: binding several views to a TabControl's items

In the current project we work on, we have a main window with several views (each with its own viewmodel) that are presented as items in a tab control. E.g: One tab item is an editor, and contains the editor view as follows:
<TabItem Header="Test Editor">
<TestEditor:TestEditorView DataContext="{Binding TestEditorViewModel}"/>
</TabItem>
Another one shows results:
<TabItem Header="Results Viewer">
<ResultViewer:ResultViewer x:Name="resultViewer1" DataContext="{Binding Path=ResultViewModel}" />
</TabItem>
etc.
I'd like to have the TabItems bound to something in the main window's viewmodel, but I can't figure out how to bind the view's name to any property without breaking the MVVM pattern. I'd like to have something like:
<TabControl.ContentTemplate>
<DataTemplate>
<TestEditor:TestEditorView DataContext ="{Binding TabDataContext}"/>
</DataTemplate>
</TabControl.ContentTemplate>
only with some binding instead of having to know at design time what type will be used as content.
Any ideas?
Usually I have the TabControl's Tabs stored in the ViewModel, along with the SelectedIndex, then I use DataTemplates to determine which View to display
View:
<Window>
<Window.Resources>
<DataTemplate DataType="{x:Type ResultViewModel}">
<ResultViewer:ResultViewer />
</DataTemplate>
<DataTemplate DataType="{x:Type EditorViewModel}">
<TestEditor:TestEditorView />
</DataTemplate>
</Window.Resources>
<TabControl ItemsSource="{Binding TabCollection}"
SelectedIndex="{Binding SelectedTabIndex}" />
</Window>
ViewModel:
public class MyViewModel : ViewModelBase
{
publicMyViewModel()
{
TabCollection.Add(new ResultsViewModel());
TabCollection.Add(new EditorViewModel());
SelectedTabIndex = 0;
}
private ObservableCollection<ViewModelBase> _tabCollection
= new ObservableCollection<ViewModelBase>();
public ObservableCollection<ViewModelBase> TabCollection
{
get { return _tabCollection };
}
private int _selectedTabIndex;
public int SelectedTabIndex
{
get { return _selectedTabIndex; }
set
{
if (value != _selectedTabIndex)
{
_selectedTabIndex = value;
RaisePropertyChanged("SelectedTabIndex");
}
}
}
}

Binding Hierarchical object data to ListBox

I'm trying to populate a ListBox with data from an object source using data binding in WPF.
The source is an ObjectDataProvider whose data is loaded in from an xml file. I read in the XML file, filling in the appropriate data structure, then set the ObjectInstance for the ObjectDataProvider to the data structure.
Here is the data structure:
public class Element
{
public decimal myValue;
public decimal df_myValue { get { return myValue; } set { this.myValue = value; } }
}
public class BasicSet
{
public Element[] elementSet;
public Element[] df_elementSet { get { return elementSet; } set { this.elementSet = value; } }
}
public class DataSet
{
public BasicSet[] basicSet;
public BasicSet[] df_basicSet { get { return basicSet; } set { this.basicSet = value; } }
}
Here is the XAML:
<UserControl.Resources>
<ResourceDictionary>
<ObjectDataProvider x:Key="TheData" />
<DataTemplate x:Key="ElementTemplate">
<StackPanel>
<TextBox Text="{Binding, Path=df_myValue}" />
</StackPanel>
</DataTemplate>
<HierarchicalDataTemplate x:Key="ElementSetTemplate" ItemsSource="{Binding Path=df_elementSet}" ItemTemplate="{StaticResource ElementTemplate}">
</HierarchicalDataTemplate>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<ListBox ItemsSource="{StaticResource TheData}" ItemTemplate="{StaticResource ElementSetTemplate}">
</ListBox>
</Grid>
Here is the code-behind where the xml data is being loaded:
private DataSet m_dataSet;
private ObjectDataProvider mODP;
public void LoadXml(EditorContext context, XmlValidator.Context validator, XmlDocument doc)
{
mODP = FindResource("TheData") as ObjectDataProvider;
XmlSerializer xs = new XmlSerializer(typeof(DataSet));
XmlReader r = new XmlNodeReader(doc.DocumentElement);
m_dataSet = (DataSet)xs.Deserialize(r);
mODP.ObjectInstance = m_dataSet;
}
The desired result is that the ListBox would have a TextBox for each element in the data structure. Note that the data structure is hierarchical for a reason. I cannot flatten the data structure to simplify the problem.
I am certain that the xml data is being loaded correctly into the data structure, because I can place a breakpoint and check it and all the data looks fine. But when I run the program, nothing shows up in the ListBox.
Any help is appreciated.
I figured out the things I was doing wrong. There were several things wrong. Here are the main points:
1) I should have been using an itemsControl
2) I didn't need to use HierarchicalDataTemplate
3) I needed three levels of controls: a TextBox inside an itemsControl inside an itemsControl... and this can be accomplished using two DataTemplates. The top-level itemsControl refers to a DataTemplate that holds an inner itemsControl. That itemsControl then refers to a DataTemplate that holds an inner TextBox.
And here is the correct xaml:
<UserControl.Resources>
<ResourceDictionary>
<ObjectDataProvider x:Key="TheData" />
<DataTemplate x:Key="ElementTemplate">
<TextBox Text="{Binding Path=df_myValue}"/>
</DataTemplate>
<DataTemplate x:Key="ElementSetTemplate">
<GroupBox Header="I am a GroupBox">
<ItemsControl ItemsSource="{Binding Path=df_elementSet}" ItemTemplate="{StaticResource ElementTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</GroupBox>
</DataTemplate>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding Source={StaticResource TheData}, Path=df_basicSet}" ItemTemplate="{StaticResource ElementSetTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
Hope this helps someone else...

Resources