WPF Tabcontrol of Datagrids - c#

I'm trying to create a databound Tabcontrol, where each TabItem contains a Datagrid, also databound.
My data structure is something like this:
public class Exercise
{
public Guid ExerciseID { get; protected set; }
public string ExerciseName { get; set; }
public List<Set> Sets { get; set; }
}
public class Set
{
public Guid SetID { get; protected set; }
public decimal Weight { get; set; }
public decimal Reps { get; set; }
public bool MaxEffort { get; set; }
}
I'm trying to bind a List<Exercise> to a TabControl, and the List<Set> for each Exercise to the DataGrid within each Tab, displaying the "Weight" and "Reps" properties in two columns. My current XAML code is below, with the relevant section labelled.
<Window x:Class="MyWorkouts.WorkoutViewer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WorkoutViewer" Height="424" Width="708"
x:Name="WorkoutDisplay">
<Grid DataContext="{DynamicResource WorkoutToDisplay}">
<Calendar SelectedDate="{Binding WorkoutDate}" Height="180" HorizontalAlignment="Left" Margin="8,12,0,0" Name="calendar1" VerticalAlignment="Top" Width="230" />
<StackPanel Height="94" HorizontalAlignment="Left" Margin="12,182,0,0" Name="spProperties" VerticalAlignment="Top" Width="238">
<StackPanel Height="30" Name="spProgram" Width="232" Orientation="Horizontal">
<Label Content="Workout Program:" Height="25" Name="lblProgram" Width="104" />
<TextBox Text="{Binding WorkoutProgram}" Height="25" Name="tbProgram" Width="120" />
</StackPanel>
<StackPanel Height="30" Name="spType" Width="232" Orientation="Horizontal">
<Label Content="Workout Type:" Height="25" Name="lblWorkoutType" Width="104" />
<TextBox Text="{Binding WorkoutType}" Height="25" Name="tbWorkoutType" Width="120" />
</StackPanel>
<StackPanel Height="30" Name="spVenue" Width="232" Orientation="Horizontal">
<Label Content="Workout Venue:" Height="25" Name="lblVenue" Width="104" />
<TextBox Text="{Binding WorkoutVenue}" Height="25" Name="tbVenue" Width="120" />
</StackPanel>
</StackPanel>
<TabControl Height="365" HorizontalAlignment="Left" Margin="260,10,0,0" Name="TCWorkoutView" VerticalAlignment="Top" Width="425">
<TabItem Header="Workout View" Name="TIView">
<StackPanel Height="335" HorizontalAlignment="Left" Name="spExercises" VerticalAlignment="Top" Width="410" Grid.ColumnSpan="2">
<ItemsControl ItemsSource="{Binding Exercises}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Expander Header="{Binding ExerciseName}" BorderThickness="1" BorderBrush="DarkBlue">
<ItemsControl ItemsSource="{Binding Sets}" DisplayMemberPath="WeightForReps" BorderThickness="1" BorderBrush="Gray"/>
</Expander>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</TabItem>
<!--The below section is the one in question-->
<TabItem Header="Edit Workout">
<TabControl ItemsSource="{Binding Path=Exercises}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<DataGrid ItemsSource="{Binding Path=Sets}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Weight" Binding="{Binding Path=Weight}"/>
<DataGridTextColumn Header="Reps" Binding="{Binding Path=Reps}"/>
</DataGrid.Columns>
</DataGrid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</TabControl>
</TabItem>
</TabControl>
</Grid>
I've got as far as having the ExerciseName property correctly displayed in each Tab (with the correct number of tabs generated, but there's no datagrid at all, instead the Tab just says MyWorkouts.Exercise (MyWorkouts is the namespace).
For a bit of background, this is meant to be the "Edit" screen for a workout logging program, and I've got the display view working correctly with a stackpanel of expanders - I need whatever solution I have to be editable, and have the changes made in the datagrid reflected in the appropriate class objects - I hope to work this out myself, but if this approach won't work, please let me know!
EDIT: My full XAML code is now listed above

Needed to set the TabControl.ContentTemplate rather than the ItemsControl.ItemTemplate. Once I switched that, I could use the DisplayMemberPath property, and everything else worked!
The XAML code I needed was:
<TabControl ItemsSource="{Binding Path=Exercises}" DisplayMemberPath="ExerciseName">
<TabControl.ContentTemplate>
<DataTemplate>
<DataGrid ItemsSource="{Binding Path=Sets}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Weight" Binding="{Binding Path=Weight}"/>
<DataGridTextColumn Header="Reps" Binding="{Binding Path=Reps}"/>
</DataGrid.Columns>
</DataGrid>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>

Related

use binds in viewmodels

I am doing a project and in one xaml page I have some Textblock with bind like this
<PivotItem Header="Lista">
<ListView x:Name="List1" ItemsSource="{x:Bind ProdutoViewModel.Produtos}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="list:Produto">`
...
<StackPanel>
<TextBlock x:Name="nome" Text="{x:Bind Nome, Mode=OneWay}" />
<TextBlock Text="{x:Bind Preco, Mode=OneWay}" />
<TextBlock Text="{x:Bind Disponivel, Mode=OneWay}" />
<TextBlock Text="{x:Bind Fornecedor, Mode=OneWay}" />
<TextBlock Text="{x:Bind Categoria, Mode=OneWay}" />
<Image Source="Assets/mouse.png" />
<Image Source="Assets/teclado.png"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</PivotItem>
And then I have another pivot like this
<PivotItem>
<PivotItem Header="Carrinho">
<ListView x:Name="Cart" ItemsSource="{x:Bind EncomendaProdutoViewModel.EncomendaProdutos}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="list:EncomendaProduto">
<StackPanel>
<Button x:Name="pay" Content="Pagar" Click="Payment_click"/>
<TextBlock Text="{x:Bind Quantidade, Mode=OneWay}" />
<TextBlock Text="{x:Bind Preco, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</PivotItem>
How can I show the data from the first pivot into the second? I have a button on the first pivotitem for each product and when I press it, I want to add the data of that product to the second pivotitem.
Create an outer view-model, like this:
<Page.DataContext>
<vm:MasterViewModel x:Name="ViewModel" />
</Page.DataContext>
class MasterViewModel
{
public ProdutoViewModel ProdutoViewModel { get; set; }
public EncomendaProdutoViewModel EncomendaProdutoViewModel { get; set; }
}
Then bind to the Pivot like this:
<Pivot>
<PivotItem>
<ListView ItemsSource="{x:Bind ViewModel.ProdutoViewModel.Produtos}"
SelectedItem="{x:Bind ViewModel.SelectedItem, Mode=TwoWay}" />
</PivotItem>
<PivotItem>
<ListView ItemsSource="{x:Bind ViewModel.EncomendaProdutoViewModel.EncomendaProdutos}"
SelectedItem="{x:Bind ViewModel.EncomendaProdutoViewModelSelectedItem, Mode=TwoWay}" />
</PivotItem>
</Pivot>
Then, handle the selection in code-behind:
class MasterViewModel
{
public ProdutoViewModel ProdutoViewModel { get; set; }
public EncomendaProdutoViewModel EncomendaProdutoViewModel { get; set; }
Producto _ProdutoViewModel;
public Producto ProdutoViewModel
{
get { return _ProdutoViewModel; }
set {
_ProdutoViewModel = value;
EncomendaProdutoViewModel.EncomendaProdutos.Add(value);
}
}
}
I hope this makes sense.
Best of luck!

wpf binding to selectedItem

In my example I'm binding to a selectedItem from a ListBox. I was wondering how can i set the binding in the stack panel so i don't have to then individually bind to each control.
Can I just bind the stack panel and then the sub controls just get bound like so (pseudo code)
<StackPanel Grid.Column="2" Content="{Binding SelectedItem.Name, ElementName=ItemList}"/>
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Text="{Binding Kids, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Text="{Binding Age, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
Code
<ListBox Grid.Column="0"
x:Name="ItemList"
Background="AliceBlue"
ItemsSource="{Binding VNodes}"
SelectedItem="{Binding SelectedVNode, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ListBox.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="Name: " />
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
</WrapPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Stretch" />
<StackPanel Grid.Column="2">
<TextBox Text="{Binding SelectedItem.Name, ElementName=ItemList, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Text="{Binding SelectedItem.Kids, ElementName=ItemList, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Text="{Binding SelectedItem.Age, ElementName=ItemList, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
In WPF, every Item has a DataContext for Bindings, You can set the DataContext of Stackpanel to
{Binding ElementName=ItemList, Path=SelectedItem},
And simply put
<TextBox Text="{Binding Age, UpdateSourceTrigger=PropertyChanged}"/>
inside the StackPanel as You wanted ;)
We have this class:
public class Jobs
{
public string Name { get; set; }
public List<string> Titles { get; set; }
}
MainViewModel (u need to make 2 property (fill these props random values)):
public MainViewModel()
{
ListJobs = new List<Jobs>();
ListJobs.Add(new Jobs() { Name = "Job1", Titles = new List<string>() {"Job1Title1","Job1Title2","Job1Title3" } });
ListJobs.Add(new Jobs() { Name = "Job2", Titles = new List<string>() {"Job2Title1","Job2Title2","Job2Title3" } });
ListJobs.Add(new Jobs() { Name = "Job3", Titles = new List<string>() {"Job3Title1","Job3Title2","Job3Title3" } });
}
private List<Jobs> listJobs;
public List<Jobs> ListJobs
{
get { return listJobs; }
set
{
if (value != listJobs)
{
listJobs = value;
OnPropertyChanged(nameof(ListJobs));
}
}
}
private Jobs selectedJob;
public Jobs SelectedJob
{
get { return selectedJob; }
set
{
if (value != selectedJob)
{
selectedJob = value;
OnPropertyChanged(nameof(SelectedJob));
}
}
}
XAML:
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<DataGrid x:Name="Jobs" Grid.Column="0" AutoGenerateColumns="False" SelectedItem="{Binding SelectedJob, UpdateSourceTrigger=PropertyChanged}" ItemsSource="{Binding ListJobs, UpdateSourceTrigger=PropertyChanged}">
<DataGrid.Columns >
<DataGridTextColumn Binding="{Binding Path=Name, UpdateSourceTrigger=PropertyChanged}" Header="Job" Width="200*" IsReadOnly="False"/>
</DataGrid.Columns>
</DataGrid>
<DataGrid x:Name="JobTitles" Grid.Column="1" AutoGenerateColumns="False" ItemsSource="{Binding SelectedJob.Titles, UpdateSourceTrigger=PropertyChanged}">
<DataGrid.Columns >
<DataGridTextColumn Header="JobTitle" Width="200*" IsReadOnly="False" Binding="{Binding}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>

Bind a textblock in a nested list member

I have these classes:
public class Datum
{
public string name{get;set;}
public string id{get;set}
public List<Brewery> breweries { get; set; }
.
.
}
public class Brewery
{
public string name { get; set; }
.
.
}
And this Listbox
<ListBox ItemsSource="{Binding}" HorizontalAlignment="Left" Height="580" Margin="0,80,0,0" VerticalAlignment="Top" Width="446">
<ListBox.ItemTemplate>
<DataTemplate>
<ListBoxItem Tap="ListBoxItem_Tap" Tag="{Binding Path=id}">
<TextBlock Name="First" Text="{Binding Path=name}" />
<TextBlock Name="Second" Foreground="Gray" Text="{Binding Path=breweries.name}" />
</ListBoxItem>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
So I create a list of Datum objects and I bind the listbox to it. In that way, the First textblock is bound to the property 'name' of the Datum class.That works perfectly. What I want is the Second textblock to be bound to the property 'name', of the first item of the Brewery list.
If breweries was not a List I would easily do that, but since I take the info from a json, I can't change that.
From your example, if I understand what you're doing correctly, you should just be able to use an index to bind to the desired item from the collection. I'm not sure it's the best approach, but it should yield the result you're after.
E.g. (adapting your example):
<ListBox ItemsSource="{Binding}" HorizontalAlignment="Left" Height="580" Margin="0,80,0,0" VerticalAlignment="Top" Width="446">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<ListBoxItem Tap="ListBoxItem_Tap" Tag="{Binding Path=id}">
<TextBlock Name="First" Text="{Binding Path=name}" />
<TextBlock Name="Second" Foreground="Gray" Text="{Binding Path=breweries[0].name}" />
</StackPanel>
</ListBoxItem>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Data Binding Help - WPF

I have those two classes:
class DownloadLink
{
public string Name { get; private set; }
public string Url { get; private set; }
//(...)
}
class DownloadGroup
{
public List<DownloadLink> Links { get; private set; }
//(...)
}
class Manager
{
public List<DownloadGroup> Groups { get; private set; }
}
Manager managerOBJ = new Manager();
I want to display this like that:
Everything will be in ListBox:
I wan to bind managerOBJ.Groups to that ListBox. - How to do it?
Than I want to create DataTamplate to display each group and all links in that group. - How to do it?
I want to do as much as possible from XAML
UPDATE:
This is what I got. It's not workig. List box is empty.
<ListBox DockPanel.Dock="Right" VerticalAlignment="Stretch" Width="500" HorizontalAlignment="Right" Background="#FFE1FFF5" HorizontalContentAlignment="Stretch" ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Visible" ItemsSource="{Binding Path=Groups}" Name="GroupsListBox">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Height="30" VerticalAlignment="Top" Width="500" >
<Grid Height="Auto" Width="500">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Content="XX MB w XX plikach" HorizontalAlignment="Stretch" Margin="0"/>
</Grid>
<ListBox HorizontalAlignment="Stretch" Height="43" Margin="0,5,0,0" Width="Auto" VerticalAlignment="Top" ItemsSource="{Binding Path=Links}">
<ListBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Path=Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
and in code behid I have:
RapideoAccount = new Rapideo();
GroupsListBox.DataContext = RapideoAccount;
The whole manager is contained in a listbox, for each downloadgroup in the manager you add an itemscontrol that contains another items control with the links in it.
This can be done by using DataTemplates:
<ListBox Name="myGroups"
ItemsSource="{Binding Path=Groups}">
<!-- each List<DownloadGroup> in the manager: -->
<ListBox.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding Path=Links}">
<!-- each Link in the Downloadgroup -->
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Name}" />
<TextBlock Text="{Binding Path=Url}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In code you would put:
Manager managerOBJ = new Manager();
myGroups.DataContext = managerOBJ;
define managerOBJ as a property in your viewmodel
binding viewmodel to your view.
binding ListBox itemssource to managerOBJ.Groups.
define DataTemplate inside ListBox to display each DownloadGroup.

Can't cast treeviewitem as treeviewitem in wpf

I've got webservice asmx, and there are classes:
Country
public string Name {get;set;}
public string Code {get;set;}
public List<Area> Areas {get;set;}
Area
public string Name {get;set;}
public string Code {get;set;}
public List<Regions> Provinces {get;set;}
Provinces
public string Name {get;set;}
public string Code {get;set;}
I bind it to mz TreeView WPF:
Country[] items = new MyService().GetListOfCountries();
structureTree.ItemsSource = items;
Code of myTree:
<UserControl x:Class="ObjectsAndZonesSimpleTree"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
<Grid>
<StackPanel Name="stackPanel1">
<GroupBox Header="Choose" Height="354" Name="groupBox1" Width="Auto">
<TreeView Name="structureTree" SelectedItemChanged="structureTree_SelectedItemChanged" Grid.Row="0" Grid.Column="0" ItemsSource="{Binding}" Height="334" ScrollViewer.VerticalScrollBarVisibility="Visible" ScrollViewer.HorizontalScrollBarVisibility="Visible" Width="Auto" PreviewMouseRightButtonUp="structureTree_PreviewMouseRightButtonUp" FontFamily="Verdana" FontSize="12" BorderThickness="1" MinHeight="0" Padding="1" Cursor="Hand" Margin="-1">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type MyService:Country}"
ItemsSource="{Binding Path=ListOfRegions}">
<StackPanel Orientation="Horizontal">
<TextBlock TextAlignment="Justify" VerticalAlignment="Center" Text="{Binding Path=Name}"/>
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type MyService:Region}"
ItemsSource="{Binding Path=Provinces}">
<StackPanel Orientation="Horizontal">
<TextBlock TextAlignment="Justify" VerticalAlignment="Center" Text="{Binding Path=Name}"/>
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type MyService:Province}"
ItemsSource="{Binding Path=ListOfCities}">
<StackPanel Orientation="Horizontal">
<TextBlock TextAlignment="Justify" VerticalAlignment="Center" Text="{Binding Path=Name}"/>
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
</GroupBox>
</StackPanel>
</Grid>
</UserControl>
This gives me null:
private void structureTree_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
TreeViewItem treeViewItem = structureTree.SelectedItem as TreeViewItem;
}
SelectedItem will actually contain a Country, Area, or Region (or null). If you really want the TreeViewItem, you can do strutureTree.ItemContainerGenerator.ContainerFromItem(structureTree.SelectedItem).
Correct. You should expect a Country as your SelectedItem. WPF works entirely different than Windows Forms did. It's all about databinding!

Categories