add buttons to table arrangement dynamically in WPF - wpf

I would like to make a 2-columns table arrangement and to add buttons to the table at runtime.
what I did is defining nested StackPanels similar to this.
<StackPanel MinWidth="500" MaxWidth="800" MaxHeight="400" HorizontalAlignment="Center">
<TextBlock HorizontalAlignment="Center" Foreground="Black" Margin="0,0,0,5" FontSize="20">Some Title</TextBlock>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button Content="" MinWidth="100" MinHeight="100" Margin="10,0,0,10"></Button>
<Button Content="" MinWidth="100" MinHeight="100" Margin="10,0,0,10"></Button>
</StackPanel>
</StackPanel>
Is this a correct starting, or there is a better and easier arrangement?

You could use a model-view implementation with ListView and bind it to a collection of items which have the respective handler for the button:
WPF:
<ListView ItemsSource={Binding MyItems}>
<ListView.View>
<GridView>
<GridViewColumn Header="Name" DisplayMemberBinding={Binding Name}></GridViewColumn>
<GridViewColumn Header="Button">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button Command={Binding ButtonPress}>Click me</Button>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
You can also set the ItemsSource inside the .cs file for your view as well, otherwise use a ViewModel class to handle your view and create a ObservableCollection<MyItemWrapper> property which will hold all the table items.
ViewModel:
public class MyViewModel
{
private ObservableCollection<MyItemWrapper> _myItems;
public MyViewModel()
{
_myItems = new ObservableCollection<MyItemWrapper>();
//// add your initial items
}
public ObservableCollection<MyItemWrapper> MyItems
{
get { return _myItems; }
}
}
View:
public partial class MyView : UserControl
{
public MyView(MyViewModel viewModel)
{
DataContext = viewModel;
InitializeComponents()
}
}
MyItem and MyItemWrapper
public class MyItem
{
public string Name { get; set; }
public object Data { get; set; }
}
public class MyItemWrapper
{
private MyItem _item;
public MyItemWrapper(MyItem item)
{
_item = item;
ButtonPress = new DelegateCommand<object>(OnButtonPress);
}
public string Name
{
get { return _item.Name; }
}
public DelegateCommand<object> ButtonPress { get; private set; }
private void OnButtonPress(object args)
{
System.Diagnostics.Debug.WriteLine("Button pressed for: " + Name);
}
}
This will ultimately be able to add/remove items at runtime by using MyItems inside the view model and have your list view always update automatically.

I got it working by using WrapPanel, and I will share what I have did, Thanks for Vlad for binding part:
<ListBox ItemsSource="{Binding CartItemC.CartItems}" >
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel x:Name="wrapPanel" MaxWidth="300" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<WrapPanel MaxWidth="300">
<Button Command="{Binding ButtonPress}">Click me</Button>
</WrapPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

You mentioned you want it dynamically, well you don't want to use StackPanel as you would end up maintaining the width and height of the controls inside it. Given you want the controls to automatically resize its own size.
The best solution is to use Grid if you really want to make a 2 column table arrangement.
Take a look at this example.
Sample code
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="28" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>

Related

How to bind each button's image source from ListView to individual item from observable collection?

I have a Listview of Buttons like this:
<ListView ItemsSource="{Binding TestList}">
<ListViewItem >
<ListView.ItemTemplate>
<DataTemplate>
<Button Name="test" Grid.Row="0" Grid.Column="10" Grid.ColumnSpan="4" Grid.RowSpan="4" VerticalAlignment="Center" Background="Transparent">
<Button.Template>
<ControlTemplate>
<Grid>
<Image Source="?????????????????????????????????????????????"/>
</Grid>
</ControlTemplate>
</Button.Template>
</Button>
</ListView.ItemTemplate>
And the code behind like this:
public class CodeBehind
{
private ObservableCollection<string> testList;
public ObservableCollection<string> TestList
{
get { return testList; }
set
{
testList = value;
}
}
public CodeBehind()
{
dummyModelList = new ObservableCollection<string>() { "/Assets/Image1", "/Assets/Image2", "/Assets/Image3"};
}
}
How to bind each button's image source to individual item from observable collection? I want to do this only in XAML.
What you are looking for is ItemTemplate (Gets or sets the DataTemplate used to display each item.) instead of buttons' template itself.
You use the ItemTemplate to specify the visualization of the data objects. If your ItemsControl is bound to a collection object and you do not provide specific display instructions using a DataTemplate, the resulting UI of each item is a string representation of each object in the underlying collection.
So when you set the ItemTemplate to an instance of DataTemplate, the DataTemplate will be used to render the items. The DataContext of the DataTemplate will be implicitly set to individual items in the bound collection, so you can bind the Image.Source to the DataContext itself using {Binding .} or {Binding}
<ListView ItemsSource="{Binding TestList}">
<ListView.ItemTemplate>
<DataTemplate>
<Button Name="test" Grid.Row="0" Grid.Column="10" Grid.ColumnSpan="4" Grid.RowSpan="4" VerticalAlignment="Center" Background="Transparent">
<Image Source="{Binding .}" />
</Button>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
You should create a DataTemplate to show any controls in your ItemTemplate of ListView:
<ListView Name="listView">
<ListView.ItemTemplate>
<DataTemplate>
<Grid Margin="2">
<Grid.RowDefinitions>
<RowDefinition Height="Auto*"/>
<RowDefinition Height="Auto*"/>
</Grid.RowDefinitions>
<Button>
<Image Source="{Binding AddressImage}" Width="20" Grid.Row="0" />
</Button>
<TextBlock Grid.Row="1" TextAlignment="Left" FontWeight="Light"
VerticalAlignment="Center" Text="{Binding UserName}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
Model:
public class Person
{
public int IdUser {get; set;}
public string UserName {get; set;}
public string AddressImage {get; set;}
}
code-behind:
public MainWindow()
{
PopulateCollection();
}
private void PopulateCollection()
{
ObservableCollection<Person> personColl = new ObservableCollection<Person>();
for (int i = 0; i <= 10; i++)
{
//here you can set any image what you want
personColl.Add(new Person() { IdUser=i, UserName="I am " + i.ToString(),
AddressImage="Assets/1.png"});
//Assets is a folder with images
}
listView.ItemsSource=personColl;
}

DataTemplate for ViewModel slow to display

I have a simple MainWindowView
<Window ...>
<Window.Resources>
<DataTemplate DataType="{x:Type local:ClassListForTeacherViewModel}">
<local:ClassListForTeacher />
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="109*"/>
<ColumnDefinition Width="408*"/>
</Grid.ColumnDefinitions>
<local:NavigationControl Grid.Column="0"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Width="Auto"/>
<ContentControl Content="{Binding MainView}"
Grid.Column="1"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Width="Auto"/>
</Grid>
<Window>
and a MainWindowViewModel
public class MainWindowViewModel : ViewModelBase
{
public MainWindowViewModel(string name)
: this(name, null)
{
}
public MainWindowViewModel(string name, ViewModelBase mainView)
{
Name = name;
MainView = mainView;
NavigateToTeacherClassesCommand = new DelegateCommand(o => NavigateToTeacherClasses());
}
//
// Properties
//
public string Name { get; private set; }
private ViewModelBase _MainView;
public ViewModelBase MainView
{
get
{
return _MainView;
}
private set
{
_MainView = value;
RaisePropertyChangedEvent("MainView");
}
}
public ICommand NavigateToTeacherClassesCommand { get; private set; }
//
// Functions
//
private void NavigateToTeacherClasses()
{
MainView = new ClassListForTeacherViewModel();
}
}
The ClassListForTeacher view only contains a ListView control with no logic (yet). When I click the Label in the NavigationControl in the MainWindowView, the ClassListForTeacher is set on the MainView property of MainWindowView and is displayed via the DataTemplate. This is barebones program; no loading data yet. My problem is the display takes too long, maybe about a full 3 seconds. It is my first time trying out MVVM, I see this is a common technique (using DataTemplate), is this normal?
I also noticed that the ViewModel for ClassListForTeacher is called twice but I have no other code calling it. One from the NavigateToTeacherClasses function, the other I don't know. When WPF actually resolves the DataTemplate (which is a UserControl), is the constructor called again? If so, How do I prevent this?
EDIT:
XAML for NavigationControl
<UserControl...
<StackPanel>
<Expander Header="Grading" IsExpanded="True">
<StackPanel Background="#FFE5E5E5">
<Label Content="My Classes">
<Label.InputBindings>
<MouseBinding Command="{Binding NavigateToTeacherClassesCommand}" MouseAction="LeftClick" />
</Label.InputBindings>
</Label>
</StackPanel>
</Expander>
</StackPanel>
</UserControl>
XAML for ClassListForTeacher:
<UserControl...
<Grid x:Name="LayoutRoot">
<ListView>
<ListView.View>
<GridView>
<GridViewColumn/>
</GridView>
</ListView.View>
</ListView>
</Grid>
</UserControl>

WPF ListBox: Binding to an ObservableCollection

I'm binding my ListBox to an ObservableCollection at runtime. Upon clicking a button, one of the items in my collection gets modified, but the corresponding ListBox item doesn't update itself accordingly. I have gone thru several similar SO articles and other help material and it seems like I'm doing everything they've asked for, but no luck. Everything seems to load and bind correctly, but when I change "IsLoading" property of an item in my collection, the Visibility of the Grid (see DataTemplate below) which is bound to IsLoading property doesn't change.
Following is my ListBox XAML:
<ListBox Name="lstItems">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Name="ListBoxGrid">
<Grid.RowDefinitions>
<RowDefinition Height="120"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<CheckBox Grid.Column="0" IsChecked="{Binding IsSelected}" />
<Image Grid.Column="1" Width="50" Stretch="Uniform" Source="{Binding Image}" />
<TextBlock Grid.Column="2" Text="{Binding Path=ImageFilePath}" />
<Grid Grid.Column="3" Visibility="{Binding IsLoading, NotifyOnTargetUpdated=True, NotifyOnSourceUpdated=True, Mode=TwoWay, BindsDirectlyToSource=True, Converter={StaticResource BooleanToVisibilityConverter1}}">
<my:LoadingAnimation x:Name="SendAnimation" VerticalAlignment="Center" HorizontalAlignment="Center" />
</Grid>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And here's my BO:
public class Order : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public bool IsSelected { get; set; }
public string ImageFilePath { get; set; }
public ImageSource Image { get; set; }
private bool mIsSending = false;
public bool IsSending
{
get { return mIsSending; }
set
{
mIsSending = value;
if (PropertyChanged != null)
PropertyChanged(null, new PropertyChangedEventArgs("IsSending"));
}
}
}
And this is how I create the collection and bind it:
ObservableCollection<Order> mOrders = new ObservableCollection<Order>();
public MainWindow()
{
InitializeComponent();
lstItems.ItemsSource = mOrders;
}
Nevermind. Sometimes it is that you spend hours digging the problem, finally get frustrated, post it on SO, and the next 2 minutes you figure it out yourself. For any future reader, the only problem was that I was sending null in PropertyChanged event. As soon as I changed that to this, things started working like a charm.

WPF/XAML using TreeViewItems in a TreeView with Databinding and advanced templates

I'm pretty new to XAML and to databinding, so hopefully this will be an easy question to someone out there...
I'm trying to build a TreeView using data binding that has a structure similar to this:
Category
Item Name
Item Name
Category
Item Name
So far, so good--I can do that. Next, I want to be able to expand the Item node and show details about that item. This works fine when I build the tree manually in the code-behind, but I want to use data binding instead. I ALMOST have this working, but as it is now, either I can't select the node OR I can't get the node to behave like a TreeViewItem where it shows just the item name when it is collapsed and can expand to show more--in my case a custom defined grid with data on it.
Here's a simplified sample of my XAML code:
<DataTemplate x:Key="ItemDataTemplate">
<TreeViewItem Header="{Binding Path=ItemName}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Price" />
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Path=Price}"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Description" />
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Path=Description}"/>
<TextBlock Grid.Row="2" Grid.Column="0" Text="Qty on Hand" />
<TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding Path=Qty}"/>
</Grid>
</TreeViewItem>
</DataTemplate>
<HierarchicalDataTemplate x:Key="CategoryDataTemplate" ItemsSource="{Binding Path=Categories}" ItemTemplate="{StaticResource ItemDataTemplate}">
<TextBlock Text="{Binding Path=CategoryName}"/>
</HierarchicalDataTemplate>
Based on this simplified example, the object that was defined in the code-behind would be something like this:
public class Category
{
public ObservableCollection<Item> Items {get; private set;}
public string CategoryName { get; set; }
//constructor and methods here
}
public class Item
{
public string ItemName {get; private set;}
public decimal Price { get; private set; }
public string Description {get; private set; }
public int Qty {get; set;}
//constructor and methods here
}
After reading this question and answer on stackoverflow, I can see that you cannot have your template be a TreeViewItem if you want to select the node. I tried getting rid of the TreeViewItem tag and going straight to the Grid which allows me to select the node, but now I can't collapse the node down to just the ItemName (header text).
I feel like it's darn close... what am I missing?
So, what you want is to make an Item look like it has more child nodes, even though its only "child" is itself, presented with more info?
Well, the most correct way of doing that might be to use an expander in ItemDataTemplate, like so:
<DataTemplate x:Key="ItemDataTemplate">
<Expander Header="{Binding Path=ItemName}" >
<Grid>
<Grid.ColumnDefinitions>
...
<Grid.RowDefinitions>
...
<TextBlock Grid.Row="0" Grid.Column="0" Text="Price" />
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Path=Price}"/>
<TextBlock ...
</Grid>
</TreeViewItem>
</DataTemplate>
The expander doesn't look exactly like a standard TreeViewItem, but that can be fixed by making your own Template for the Expander.
Now, a slightly more hacky way of doing it would be to have a list of Items in the Item class, where each Item exposes itself as the only list element. Then, ItemDataTemplate would need to be a HierarchicalDataTemplate which referenced another, more detailed, "ItemDetailsTemlate":
public class Item
{
...
public List<Item> InfoWrapper
{
get { return new List<Item>() { this }; }
}
}
Xaml:
<DataTemplate x:Key="ItemDetailsTemplate">
<Grid>
<Grid.ColumnDefinitions>
...
<Grid.RowDefinitions>
...
<TextBlock Grid.Row="0" Grid.Column="0" Text="Price" />
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Path=Price}"/>
<TextBlock ...
</Grid>
</DataTemplate>
<HierarchicalDataTemplate x:Key="ItemDataTemplate" ItemsSource="{Binding Path=InfoWrapper}" ItemTemplate="{StaticResource ItemDetailsTemplate}" >
<TextBlock Text="{Binding Path=ItemName}"/>
</HierarchicalDataTemplate>

How do I bind list items to an Accordian control from the Silverlight 3 Toolkit?

I have a list of objects in a model. I wish to show elements of the DTO's in the list in my AccordianItem panels. The model is like this:
public class MyModel
{
public List<AnimalDTO> Items { get; set; }
public MyModel()
{
Items = new List<AnimalDTO>
{
new AnimalDTO() {Title = "Monkey", ImageUri = "Images/monkey.jpg"},
new AnimalDTO() {Title = "Cow", ImageUri = "Images/cow.jpg"},
};
}
}
public class AnimalDTO
{
public string Title { get; set; }
public string LongDescription { get; set; }
public string ImageUri { get; set; }
public string NavigateUri { get; set; }
}
I want to show the image in the background image of AccordianItems and lay the LongDescription over a portion of the image.
If I hard code it, I can get the image in the AccordianItem thus...
<layoutToolkit:AccordionItem x:Name="Item2" Header="Item 2" Margin="0,0,10,0" AccordionButtonStyle="{StaticResource AccordionButtonStyle1}" ExpandableContentControlStyle="{StaticResource ExpandableContentControlStyle1}" HeaderTemplate="{StaticResource DataTemplate1}" BorderBrush="{x:Null}" ContentTemplate="{StaticResource CarouselContentTemplate}">
<layoutToolkit:AccordionItem.Background>
<ImageBrush ImageSource="Images/cow.jpg" Stretch="None"/>
</layoutToolkit:AccordionItem.Background>
</layoutToolkit:AccordionItem>
When I try it with a binding like <ImageBrush ImageSource="{Binding Path={StaticResource MyContentTemplate.ImageUri}}" Stretch="None"/> or if I try it with <ImageBrush ImageSource="{Binding Path=Items[0].ImageUri}" Stretch="None"/>
, it throws XamlParseException.
Edit:
I'm able to get some binding of the text over hard-coded images with the following StaticResource (NOTE: I'm hard-coding Items[2], I'm not sure how to index it)
<DataTemplate x:Key="CarouselContentTemplate">
<Grid Width="650" Height="420">
<Grid.RowDefinitions>
<RowDefinition Height="0.476*"/>
<RowDefinition Height="0.524*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0"
x:Name="Title"
Text="{Binding Items[2].Title}"
Foreground="Black" FontSize="12"></TextBlock>
<TextBlock Grid.Row="1" Grid.Column="0"
x:Name="LongDescription"
Text="{Binding Items[2].LongDescription}"
TextWrapping="Wrap"FontSize="8"></TextBlock>
</Grid>
</DataTemplate>
Is there a way to index the Items collection in the DataTemplate? Furthermore how do I get the Image to bind rather than hard-coding them in each AccordianItem? Any help in the right direction would be appreciated, most especially how to bind and lay text over an image.
To bind to a collection it must be referenced with ItemsSource="{Binding Items}", where in this case Items is my collection MyModel.Items
<layoutToolkit:Accordion
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
ExpandDirection="Right"
Style="{StaticResource AccordionStyle1}"
AccordionButtonStyle="{StaticResource AccordionButtonStyle1}"
MaxHeight="420" MaxWidth="800"
ItemsSource="{Binding Items}" Margin="8,0,-8,-12" Grid.Row="3"
>
<layoutToolkit:Accordion.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}"></TextBlock>
</DataTemplate>
</layoutToolkit:Accordion.ItemTemplate>
Note that a collection should be bound with ItemsSource, which is plural as a mnemonic. and individual members of elements are bound within control of <layoutToolkit:Accordian.ItemTemplate> Here I am showing MyCollection.Title in a TextBlock control. I shall update this with full code or a link to my blog for a full example later.

Resources