I want to build a dynanmic list of buttons (or whatever element is in the template, doesn't really matter.., my XAML is:
<Button Content="Add New Button" VerticalAlignment="Center" Click="AddNewButton" HorizontalAlignment="Center"/>
<ItemsControl ItemsSource="{Binding ButtonsList}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Height="40" MinWidth="40" Content="{Binding Name}" Margin="2"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
My code behind looks like this:
public MainWindow()
{
InitializeComponent();
ButtonsList = new ObservableCollection<ShortcutButton>();
}
public class ShortcutButton
{
public string Name { get; set; }
}
public ObservableCollection<ShortcutButton> ButtonsList { get; set; }
private void AddNewButton(object sender, RoutedEventArgs e)
{
tblock.Text += "Button Clicked" + Environment.NewLine;
ButtonsList.Add(new ShortcutButton() { Name = "New button title" });
}
When I debug, I can see that objects are being added to the ButtonsList Observable Collection, but it does not update in the bound WrapPanel in ItemsControl.. What am I doing wrong?
Your Binding is incorrect, by default it will try to search for a property named ButtonList in the DataContext. To bind your ItemsSource to your ButtonList defined in the MainWindow you should change the RelativeSource like this:
<ItemsControl ItemsSource="{Binding Path=ButtonsList, RelativeSource={RelativeSource AncestorType={x:Type local:MainWindow}}}">
Related
I have a Listbox which is bound to a DataTemplate that has another Listbox on it.
On DataTemplate there is a button that I want to use for adding items to DataTemplate ListBox, but I can't find a solution to do this.
Here is my listbox:
<Button Width="200" Content="Add Question" x:Name="btnAddQuestion" Click="btnAddQuestion_Click"/>
<StackPanel Orientation="Horizontal">
<ListBox Margin="5" x:Name="lvQuestions" ItemTemplate="{StaticResource TemplateQuestionTitle}">
</ListBox>
</StackPanel>
And this is DataTemplate:
<DataTemplate x:Key="TemplateQuestionTitle">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBox materialDesign:HintAssist.Hint="Enter question" MinWidth="200" Style="{StaticResource MaterialDesignFloatingHintTextBox}"/>
<Button Content="+" Command="{Binding Source={x:Reference ThisPage},Path=DataContext.Command}" />
</StackPanel>
<ListBox ItemsSource="{Binding MyItems}" MinHeight="50">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox>
</TextBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
This is code behind on my page:
public partial class UIBuilder:Window
{
private CommandVm _commandVm;
public UIBuilder()
{
InitializeComponent();
_commandVm = new CommandVm();
DataContext = _commandVm;
}
private void btnAddQuestion_Click(object sender, RoutedEventArgs e)
{
lvQuestions.Items.Add(null);
}
}
I have implemented this code on my ViewModel in order to add items to datatemplate ListBox:
public class CommandVm
{
public ObservableCollection<TextBox> MyItems { get; set; }
public CommandVm()
{
MyItems = new ObservableCollection<TextBox>();
Command = new RelayCommand<TextBox>(Execute);
}
private void Execute(TextBox textBox)
{
MyItems .Add(textBox);
}
public ICommand Command { get; set; }
}
I use to catch the Execute() function on button "+" click command, but my code doesn't add any ListBox item.
MyItems is a property of the parent view model which means that you should bind to it like this:
<ListBox ItemsSource="{Binding DataContext.MyItems,
RelativeSource={RelativeSource AncestorType=Window}}" MinHeight="50">
This also means that you are using one single collection of items for all questions. Besides this obvious design flaw, a view model should not contain any TextBox elements. This basically breaks what the MVVM pattern is all about.
What you should do to make this example MVVM compliant is to create a Question class that has a collection of items, e.g.:
public class Question
{
public Question()
{
AddAnswerCommand = new RelayCommand<object>(Execute);
}
private void Execute(object obj)
{
Items.Add(new Answer());
}
public ObservableCollection<Answer> Items { get; }
= new ObservableCollection<Answer>();
public ICommand AddAnswerCommand { get; }
}
public class Answer { }
The window's view model should then have a collection of questions:
public class CommandVm
{
public CommandVm()
{
AddQuestionCommand = new RelayCommand<object>(Execute);
}
public ObservableCollection<Question> Questions { get; }
= new ObservableCollection<Question>();
public ICommand AddQuestionCommand { get; }
private void Execute(object obj)
{
Questions.Add(new Question());
}
}
The view and the bindings could then be defined like this:
<Window.Resources>
<DataTemplate x:Key="TemplateQuestionTitle">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBox MinWidth="200" />
<Button Content="+" Command="{Binding AddAnswerCommand}" />
</StackPanel>
<ListBox ItemsSource="{Binding Items}" MinHeight="50">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</Window.Resources>
<StackPanel>
<Button Width="200" Content="Add Question" Command="{Binding AddQuestionCommand}"/>
<ListBox Margin="5"
ItemsSource="{Binding Questions}"
ItemTemplate="{StaticResource TemplateQuestionTitle}" />
</StackPanel>
This setup lets you add individual elements to each separate question.
I want to repeat the Border element inside the outer WrapPanel:
<WrapPanel x:Name="filterWrapper" Orientation="Horizontal">
<Border>
<WrapPanel>
<TextBlock Text="{Binding FilterName}"/>
<TextBlock Text="x"/>
</WrapPanel>
</Border>
</WrapPanel>
This is the depending class:
public class FilterField
{
public String FilterName { get; set; }
}
I have a collection of FilterField objects, which should result in:
<WrapPanel x:Name="filterWrapper" Orientation="Horizontal">
<Border>
<WrapPanel>
<TextBlock Text="Test_1"/>
<TextBlock Text="x"/>
</WrapPanel>
</Border>
<Border>
<WrapPanel>
<TextBlock Text="Test_2"/>
<TextBlock Text="x"/>
</WrapPanel>
</Border>
<Border>
<WrapPanel>
<TextBlock Text="Test_3"/>
<TextBlock Text="x"/>
</WrapPanel>
</Border>
</WrapPanel>
How can I achieve this?
You can use the ItemsControl to display a collection of items with a specific template in a specific Panel:
<ItemsControl ItemsSource="{Binding Items}">
<!-- Place all items in a WrapPanel -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel x:Name="filterWrapper" Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- Define the look of each item -->
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type wpfapp1:FilterField}">
<Border>
<WrapPanel>
<TextBlock Text="{Binding FilterName}"/>
<TextBlock Text="x"/>
</WrapPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The DataTemplate you define in ItemsControl.ItemTemplate gets applied to all items in the source collection, and each item gets added in the ItemsControl.ItemsPanel that you define, here a WrapPanel.
This is assuming you have a a collection Items of type List<FilterField> in your view model, like this:
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
DataContext = new MyViewModel();
}
}
public class MyViewModel {
public List<FilterField> Items { get; set; } = new List<FilterField> {
new FilterField() { FilterName = "Test_1"},
new FilterField() { FilterName = "Test_2"},
new FilterField() { FilterName = "Test_3"},
};
}
public class FilterField {
public string FilterName { get; set; }
}
I have a ListBox which creates a TextBlock for every item in my Dictionary and I need to create a click event and have the possibility to get anything from the TextBlock (Tag in example).
Is this even possible? I found many similar questions, but nothing that would work for me.
My ListBox:
<ListBox x:Name="listbox">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Tag="{Binding Path=Key}"
Text="{Binding Path=Value.ImieNazwisko}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
To simplify things lets say your dictionary is some thing like this :
public Dictionary<string,MyItem> MyDictionary { get; set; }
and your model :
public class MyItem
{
public string ImieNazwisko { get; set; }
}
and you set the ListBox ItemSource from the code behind like this :
InitializeComponent();
MyDictionary = new Dictionary<string, MyItem>()
{
{"key1",new MyItem() {ImieNazwisko = "one"} },
{"key2",new MyItem() {ImieNazwisko = "two"} }
};
Listbox.ItemsSource = MyDictionary;
You could simply handle the MouseDown event and retrieve the Tag property from the sender:
<ListBox x:Name="Listbox" Margin="225,0,0,0" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Tag="{Binding Path=Key}" Text="{Binding Path=Value.ImieNazwisko}" MouseDown="UIElement_OnMouseDown"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
the handler
private void UIElement_OnMouseDown(object sender, MouseButtonEventArgs e)
{
var tag=(sender as TextBlock).Tag;
}
When I tap on listBox item i get a SubItem in "selectionChanged" event. I need to get Title as well. How i can achieve it?
public class Data
{
public string Title { get; set; }
public List<SubItem> SubItems { get; set; }
public Data()
{
SubItems = new List<SubItem>();
}
}
<phone:LongListSelector ItemsSource="{Binding DataCollection}" Grid.Row="0">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Title}" Padding="5" />
<TextBlock Text="{Binding ImageSource}" Padding="5"/>
</StackPanel>
<ListBox ItemsSource="{Binding SubItems}" SelectionChanged="ListBox_SelectionChanged">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding SubItemTitle}" Margin="0,0,12,0" Padding="10" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
In the SelectionChanged event, you can retrieve your ListBox by casting the sender parameter. From there, you can retrieve your Data object by casting the datacontext:
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var listBox = (ListBox)sender;
var data = (Data)listBox.DataContext;
System.Diagnostics.Debug.WriteLine(data.Title);
}
Under your selectionChangedevent try this :
string text = (listBox.SelectedItem as ListBoxItem).Content.ToString(); //listBox is the name of the Listbox
a better reference could be this:
Getting selected item string from bound ListBox
Hope it helps!
I'm attempting to databind to a Windows Phone 8 Toolkit Expander view with the following XAML and C# class. I know that the DataContext is set properly because the Headers have the proper text. However, the rest of the items aren't set properly (except for the ExpanderTemplate)
<phone:PanoramaItem Header="Skill Sheet">
<ListBox Name="SkillSheet" ItemsSource="{Binding}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<toolkit:ExpanderView Header="{Binding}"
ItemsSource="{Binding}"
IsNonExpandable="False">
<toolkit:ExpanderView.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding groupName}" FontFamily="{StaticResource PhoneFontFamilySemiBold}" LineHeight="{StaticResource LongListSelectorGroupHeaderFontSize}" />
</DataTemplate>
</toolkit:ExpanderView.HeaderTemplate>
<toolkit:ExpanderView.ExpanderTemplate>
<DataTemplate>
<TextBlock Text="Test" />
</DataTemplate>
</toolkit:ExpanderView.ExpanderTemplate>
<!--This is the area that is not getting databound-->
<toolkit:ExpanderView.ItemTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding skillNames}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding skill}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</toolkit:ExpanderView.ItemTemplate>
</toolkit:ExpanderView>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</phone:PanoramaItem>
And here are the classes that the XAML is getting bound to:
public class TreeMapSkill
{
public string skill { get; set; }
}
public class TreeMapping
{
public string groupName { get; set; }
public List<TreeMapSkill> skillNames { get; set; }
public TreeMapping()
{
skillNames = new List<TreeMapSkill>();
}
}
public class TreeMappingList
{
public List<TreeMapping> mapping { get; set; }
public TreeMappingList() { }
public TreeMappingList(Dictionary<string, List<string>> map)
: base()
{
this.mapping = new List<TreeMapping>();
foreach (string key in map.Keys)
{
TreeMapping tMap = new TreeMapping();
tMap.groupName = key;
foreach (string val in map[key])
tMap.skillNames.Add(new TreeMapSkill() { skill = val });
this.mapping.Add(tMap);
}
}
The Dictionary in the constructor is simply a list of skills associated to a specific group. I can also provide a sample object if it's needed for additional reference.
Why are you adding a ListBox inside the Expander's ItemTemplate? It is already a controls collection so you don't need a ListBox in there. Just put your DataTemplate inside.
<toolkit:ExpanderView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding skill}" />
</DataTemplate>
</toolkit:ExpanderView.ItemTemplate>
The second thing is you need to specify the property path on the binding of the ItemSource property for the expander.
<toolkit:ExpanderView Header="{Binding}"
ItemsSource="{Binding skillNames}"
IsNonExpandable="False">