Buttons in StackPanel bound to List - wpf

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"/>

Related

Auto generate WPF buttons based on a property in a collection using binding

I want to bind a property in a collection to auto generate buttons, can this be achieved using data binding?
What I tried to do without binding is the following:
var servicesList = sad.GetAllServices().ToList();
foreach (var service in servicesList)
{
var btn = new Button
{
Name = "btnService_" + service.Id,
Content = service.NameEn,
Width = 200,
Height = 50,
Margin = new Thickness(20)
};
btn.Click += (sender, e) =>
{
// Handling click event...
};
PnlServices.Children.Add(btn);
}
I want to create a button for each item in the collection and make the content of the button equal to item.NameEn
Something like this
public class ViewModel
{
public ObservableCollection<Service> servicesList => new ObservableCollection<Service>(sad.GetAllServices().ToList());
}
XAML
<Window>
<Window.Resources>
<local:ViewModel x:Key="ViewModel"></local:ViewModel>
</Window.Resources>
<Grid>
<ItemsControl DataContext="{Binding Source={StaticResource ViewModel}}"
ItemsSource="{Binding Path=servicesList}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Path=Name}"
Width="200"
Height="50"
Margin="20"
Click="OnClickHandler"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
First generate collection (Array / List / ObservableCollection) .
Bind an ItemsControl Control to the collection in step 1 above to generate buttons.
Note : You cannot bind Name property of a control.
Good tutorial about ItemsControl
Eg;
var items = new[] { new { Description = "Btn1" }, new { Description = "Btn2" } };
BtnList.ItemsSource = items;
<ItemsControl x:Name="BtnList">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Description}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Convert to servicesList to an public ObservableCollection and bind to ListBox (or ItemsControl).
Assuming that your view model is Service.
private ObservableCollection<Service> serviceList = new ObservableCollection<Service>();
public ObservableCollection<Service> ServiceList
{
get
{
return serviceList;
}
}
<ListBox ItemsSource="{Binding ServicesList}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Button Height="50" Width="200" Content="{Binding NameEn}" Margin="20" Tag="{Binding Id}" Click="ButtonClickHandler" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</List>
Now create a click handler and given change it in xaml for Click event.

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

Reusable usercontrol with radiobuttons are being unchecked unexpectedly

I'm having an issue with the RadioButton when using a reusable UserControl. For some reason I am unable to check a radio button of each 'Chooser' control but instead when one radio button is checked, all other radio buttons in the current chooser and in the other choosers are unchecked. Does anyone know how to change this code so that I can have a checked item in each 'chooser' user control. The user control must be able to be dynamically built using a collection. In a real world example, each 'chooser' usercontrol will have different text values.
MainPage.xaml
<StackPanel x:Name="LayoutRoot" Background="White">
<radioButtonTest:Chooser />
<radioButtonTest:Chooser />
<radioButtonTest:Chooser />
</StackPanel>
Chooser.xaml
<UserControl x:Class="RadioButtonTest.Chooser"
xmlns ...>
<StackPanel x:Name="LayoutRoot" Orientation="Horizontal">
<TextBlock x:Name="MyLabel" Text="Choices:" VerticalAlignment="Center" Margin="0,0,10,0" />
<ItemsControl x:Name="MyChooser" Height="25" VerticalAlignment="Top" HorizontalAlignment="Left">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<RadioButton Height="22" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Content="{Binding}" MinWidth="35" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</UserControl>
Chooser.xaml.cs
public partial class Chooser
{
public Chooser()
{
InitializeComponent();
// adding some test items to the itemscontrol
MyChooser.ItemsSource = new ObservableCollection<string>
{
"first",
"second",
"third"
};
}
}
Turns out I needed to use the GroupName property of the RadioButton to dictate the groupings. I did this by changing the item source to a custom class with a Group property of type string and binding this property to the GroupName property on the RadioButton.
XAML:
<DataTemplate>
<RadioButton ... Content="{Binding Name}" GroupName="{Binding Group}" />
</DataTemplate>
C#:
public Chooser()
{
InitializeComponent();
// the groupName needs to be the same for each item
// in the radio group, but unique for each separate group.
var groupName = Guid.NewGuid().ToString();
MyChooser.ItemsSource = new ObservableCollection<RadioButtonGroupItem>
{
new RadioButtonGroupItem {Group = groupName, Name = "first"},
new RadioButtonGroupItem {Group = groupName, Name = "second"},
new RadioButtonGroupItem {Group = groupName, Name = "third"}
};
}
public class RadioButtonGroupItem
{
public string Name { get; set; }
public string Group { get; set; }
}
Hope this helps someone.

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

How to read dynamical added check box?

I am adding checkboxes dynamically to silverlight stackpanel object as follows:
foreach (String userName in e.Result)
{
CheckBox ch = new CheckBox();
ch.Name = userName;
ch.Content = userName;
ContentStackPanel.Children.Add(ch);
}
How do I read back those controls to detect which of them are checked.
You can use databinding to checkbox list. Something like this:
Use a Listbox to create the check list:
<ListBox x:Name="chkList" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<CheckBox Content="{Binding userName}" IsChecked="{Binding Checked, Mode=TwoWay}"></CheckBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Then in your code just set the chklist itemSource to an ObservableCollection with your object
chkList.ItemsSource = ....
You should probably avoid creating checkboxes in code like this. Something that might be useful for you is a mini "ViewModel" for the checkbox. Something like this:
public class Option
{
public string Text {get; set;}
public bool IsChecked {get; set;}
}
Then, you can have a collection of these options like this:
var options = new ObservableCollection<Option>();
Once this is populated, you can bind the ObservableCollection to an ItemsControl:
<ItemsControl ItemsSource="{Binding options}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Text}" IsChecked="{Binding IsChecked}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
That XAML will create the CheckBoxes for you for ever option you added to your options collection. The really great thing is that you can now ask you options collection which options have been selected:
var selectedNames = from option in options
where option.IsChecked
select option.Text;
Using data binding and templates is a technique you should get familiar with in Silverlight/WPF. It is a really important concept, and it will let you do amazing things in you application.

Resources