There is list with columns to display. I am using ListView with data templates for cells. And my problem is to access both: row and column from template.
Below is demo, xaml:
<ListView x:Name="listView" ItemsSource="{Binding Items}">
<ListView.Resources>
<GridViewColumn x:Key="Column" x:Shared="False">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock>
<Run Text="{Binding WhatHere}" /> <!-- problem here -->
<Run Text="{Binding Mode=OneWay}" />
</TextBlock>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</ListView.Resources>
<ListView.View>
<GridView />
</ListView.View>
</ListView>
and the code:
public partial class MainWindow : Window
{
public List<string> Items { get; } = new List<string> { "1", "2", "3" };
public MainWindow()
{
InitializeComponent();
DataContext = this;
var a = (GridViewColumn)listView.FindResource("Column");
a.Header = "a";
((GridView)listView.View).Columns.Add(a);
var b = (GridViewColumn)listView.FindResource("Column");
b.Header = "b";
((GridView)listView.View).Columns.Add(b);
}
}
will produce
My aim is to have:
a b
a1 b1
a2 b2
a3 b3
Possible? How do I pass column to DataTemplate ? In fact I want to simply know to which column current cell belongs, but please consider following:
This is simplified case.
In reality data templates are more complicated: many elements with bindings, triggers, etc.
In reality column related data are more complicated than just header.
Columns will be generated at runtime with different headers, etc.
Adding columns and setting Header in code-behind is not a problem, DataTemplate is.
I was thinking to use attached property on GridViewColumn, however, it's not parent of cells in the visual tree:
If you want to pass parameter to data template, then you need more MVVM (c) unknown wpf programmer
#mm8, was right, my design lack one more abstraction to hold column information (column name). I've to create column view model (simplified again):
public class ViewModelColumn
{
public string Column { get; }
public ViewModelColumn(string column)
{
Column = column;
}
}
and the code to add column will become something like
var a = new FrameworkElementFactory(typeof(ContentControl));
a.SetValue(ContentControl.ContentProperty, new ViewModelColumn("a"));
((GridView)listView.View).Columns.Add(new GridViewColumn
{
Header = "a",
CellTemplate = new DataTemplate { VisualTree = a }
});
Cell data template is created in code behind. The idea is to supply ContentControl for all cells with column instance bound to Content, then the view needs another data template (this time fully defined in xaml), to know how to visualize it:
<DataTemplate DataType="{x:Type local:ViewModelColumn}">
<TextBlock>
<Run Text="{Binding Column, Mode=OneTime}" />
<Run Text="{Binding DataContext,RelativeSource={RelativeSource AncestorType={x:Type ListViewItem}}, Mode=OneWay}" />
</TextBlock>
</DataTemplate>
Such cell DataContext contains column information. To access row (access item from Items), we have to use ListViewItem.DataContext.
Now the view will looks like this:
I am pretty happy about this solution, mostly about combination of things what makes it working, but I guess it could be improved. Hopefully after posting the answer the question become clearer.
Related
This question already has an answer here:
ItemsControl ItemTemplate Binding
(1 answer)
Closed 2 months ago.
I have a user control StudentView.xaml that is something like this:
<Grid>
<TextBlock Text="{Binding StudentName}"/>
<TextBlock Text="{Binding StudentID}"/>
</Grid>
I also have a model Student.cs that is something like this:
private string _StudentName = null;
public string StudentName{
get => _StudentName;
set {_StudentName = value; OnPropertyChange("StudentName");}
}
private string _StudentID = null;
public string StudentID{
get => _StudentID ;
set {_StudentID = value; OnPropertyChange("StudentID");}
}
In my ViewModel MainViewModel I have a collection of Student's like this:
ObservableCollection<Student> Students = new ObservableCollection<Student>();
// Do something here to populate Students
NumOfStudents = Students.Count();
In my main window, I have something like this:
<StackPanel>
<local:StudentView/>
</StackPanel>
I am very new to WPF and seeking for help. I know how to bind local:StudentView to one student. My question is that is there a way to use local:StudentView as many as time as NumOfStudents. For example, if I have 5 students, the main window will display StudentView 5 times.
What you most likely want is a ItemsControl. You will set the ItemsSource property to your observable collection and define a ItemTemplate to define how the items should be rendered (StudentView.xaml). Finally, since you are using a StackPanel, that will be defined as your ItemsPanelTemplate.
You'll want do something like...
<ItemsControl
ItemsSource="{Binding Students}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:StudentView/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
In My WPF Application I am using MVVM Model. Datagrid Contains Textbox and Label, when provide the input at the run time in the Textbox, dynamically a description will show in label as per the input in the same row.
But the problem is when I provided the input to a textbox, all the textbox with in the datagrid reflect the same input value as their id is not different in grid. how can I solve this problem.
<Grid>
<DataGrid Name="c1DataGrid1" ItemsSource="{Binding CreditInfo}" AutoGenerateColumns="False" CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Credit" Binding="{Binding Path=Credit}"/>
<DataGridTemplateColumn Header="Percentage">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBox Text="{Binding Path=DataContext.CreditPercentage, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" />
<b:Interaction.Triggers>
<b:EventTrigger EventName="LostFocus">
<b:InvokeCommandAction Command="{Binding Path= DataContext.LostFocusCommand, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" CommandParameter="{Binding}">
</b:InvokeCommandAction>
</b:EventTrigger>
</b:Interaction.Triggers>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Description">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBlock Width="440" Text="{Binding PercentageDescription}"/>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
If I've understood your code correctly you have a grid with 3 columns. The first column contains some value the second column contains textbox where you can insert a value and the third column contains a textbox that calculates percentage of the first column value taking second column value as the percent.
i.e. you have Credit=50 you type 10 into the second column's textbox and you want 5 to appear in the third column.
If that's correct then there is an easier way to achieve what you want.
You create two new properties in the view model for the items bound to your grid. The first property will contain whatever is entered into the textbox of the second column:
private int _creditPercentage;
public int CreditPercentage
{
get { return _creditPercentage; }
set
{
if (value == _creditPercentage)
return;
_creditPercentage= value;
OnPropertyChanged("CreditPercentage");
OnPropertyChanged("PercentageDescription");
}
}
The second property is going to contain the result of the calculation:
public String PercentageDescription
{
get { return Convert.ToString(Math.Round((double)Credit*Percentage/100), CultureInfo.InvariantCulture); }
}
Now you bind the Percentage property to your TextBox in the second column. And PercentageDescription to your third column:
<DataGridTemplateColumn Header="Percentage">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding CreditPercentage}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Description" Binding="{Binding Path=PercentageDescription}"/>
You might also want to implement some input validation in that textbox in the second column to insure that user can only enter digits.
its worked for me by applying the OnPropertyChanged("CreditPercentage"); in the creditinfo property, also define the percentageDescription property in creditmodel.
public ObservableCollection<Credits> CreditInfo
{
get
{
return infos;
}
set
{
infos = value;
OnPropertyChanged("CreditInfo");
OnPropertyChanged("CreditPercentage");
//OnPropertyChanged("PercentageDescription");
}
}public string PercentageDescription
{
get
{
return percentageDescription;
}
set
{
percentageDescription = value;
OnPropertyChanged("PercentageDescription");
}
}
I am creating a Windows phone 8.1 application
I have a generated a list of textboxes RepTb in a listview. But I cannot figure out a way to get the text in these textboxes.
<Grid>
<Grid.Resources>
<DataTemplate x:Name="items">
<TextBox Name="RepTb" Header="{Binding}"
Width="100" HorizontalAlignment="Left"
LostFocus="RepTb_LostFocus"/>
</DataTemplate>
</Grid.Resources>
<ListView x:Uid="RepListView"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource items}"
Name="RepList"/>
</Grid>
Code used to create textboxes:
List<string> setlist = new List<string>();
int set = 10;
for (int i = 1; i <= set; i++ )
setlist.Add("Reps in Set " + i);
RepList.DataContext = setlist;
Can anyone tell me how to iterate through RepList and get the content off the textboxes?
You can use a TwoWay data binding to get value of your textbox.
First you need to create a class which will contain your data.
public class RepItem
{
public string Header {get; set;}
public string Value {get;set;}
}
Then inject a List instead of a List
List<RepItem> setlist = new List<RepItem>();
int set = 10;
for (int i = 1; i <= set; i++ )
setlist.Add(new RepItem() {Header = "Reps in Set " + i});
RepList.DataContext = setlist;
Finally, bind the TextBox Text property to Value property of your RepItem object :
<DataTemplate x:Name="items">
<TextBox Name="RepTb"
Header="{Binding Header}"
Text="{Binding Value, Mode=TwoWay}"
Width="100"
HorizontalAlignment="Left"
LostFocus="RepTb_LostFocus"/>
</DataTemplate>
You can use Text="{Binding Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" if you want that the Value property is updated each time you type a character, otherwise it will be updated when hte focus on the TextBox is lost.
You will be able to iterate through your List and get all the values
((List<RepItem>)RepList.DataContext).Select(repItem => repItem.Value);
I have a combobox which is populated from a list of names which were obtained from selecting from an Observable collection. However, associated with those names is an ID also in that Observable collection. The goal is when the user selects a new name (Say changes "John" to "Jill") I will be able to obtain the ID, not just the name. The only way I can think of doing this is storing the ID also in the combobox somehow. But I don't know how to do that with binding.
<DataGridTemplateColumn Header="Name ">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox x:Name="namescombo" ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=DataContext.Names}"
SelectedItem="{Binding Name, UpdateSourceTrigger=PropertyChanged}" FontSize="12" Background="White" FontFamily="Cambria" BorderBrush="White" BorderThickness="0"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
C#
ObservableCollection<Name> Names = new ObservableCollection<Name>();
Name twofields = new Name();
var NamesQuery =
from p in dataEntities.Names
select new { p.Name, p.Id };
foreach (var p in NamesQuery)
{
Names.Add(new Name
{
ID = p.Id,
Name = p.Name
});
}
Names = Names.Select(p => p.Name).Distinct().ToList();
A ComboBox contains properties for both the DisplayMemberPath and the SelectedValuePath, so you could use it to tell the ComboBox to identify items by the "Id" property, but display the "Name" property to the user.
For example,
<DataGridTemplateColumn Header="Name ">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=DataContext.Names}"
DisplayMemberPath="Name"
SelectedValuePath="Id"
SelectedValue="{Binding SelectedId}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
I would recommend using SelectedValue over SelectedItem because WPF compares SelectedItem by .Equals() which defaults to comparing items by reference, and if your SelectedItem is not the exact same reference as the item in your ItemsSource, it won't get selected.
For example, SelectedItem = new Person(1, "Test"); would probably not set the selected item correctly, while SelectedItem = ItemsSource[0] would since it refers to an item that exists in the ItemsSource.
Also, it frequently makes more sense to store just the Id of the selected item on a row instead of the entire object :)
You can bind directly to Name object collection and set DisplayMemberPath to Name property so that strings are shown on GUI but in essence you have complete object binded to comboBox.
This way you can bind SelectedItem to Name object and can access Id and Name property.
<ComboBox ItemsSource="{Binding Names}" // Collection of name objects.
DisplayMemberPath="Name"
SelectedItem="{Binding SelectedNameObject}"/> // Object of type Name.
I'm new to WPF and I'll try to keep this one short.
What I'm trying to achieve is to display a table, in which cell values are unrelated to each other and each one represents a seperate object.
To be more precise, I need a calendar-like view with associated tasks on each day, like this:
The number of displayed days is variable.
What matters is that days are listed chrolonogically and the order of tasks on a specific day is preserved (pretty much like it was a list of ListBoxes).
So how do I achieve that?
DataGrid seems to only bind rows to data. I thought of implementing a list of adapters with variable number of DependencyProperties which would pretend to be rows. But this seems to be a bit too much complicated for such a simple table.
I also looked into how to make DataGrid horizontal, but it's even more additional code.
Any help apreciated. Cheers :)
You can just use a ListBox, or even just an ItemsControl for each day and have any number of them... you just need to structure your data correctly. Let's say you have a Day class with a Date and a collection called Tasks:
public class Day // Implement INotifyPropertyChanged correctly here
{
public DateTime Day { get; set; }
public ObservableCollection<string> Tasks { get; set; }
}
Now in your view model, you just need a collection of Day instances:
public ObservableCollection<Day> Days { get; set; }
Then you just need a DataTemplate to define your ListBox for each Day instance:
<DataTemplate DataType="{x:Type DataTypes:Day}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding Date, StringFormat={}{0:MMM d}}" />
<ListBox Grid.Row="1" ItemsSource="{Binding Tasks}" />
</Grid>
</DataTemplate>
Finally, add a ListBox, or ItemsControl to display the collection of Days and set the ItemsPanel to a StackPanel with its Orientation property set to Horizontal:
<ItemsControl DockPanel.Dock="Top" ItemsSource="{Binding Days}" Name="overlayItems">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
Add some test data and your away:
Days = new ObservableCollection<Day>();
Days.Add(new Day() { Date = new DateTime(2014, 5, 1), Tasks = new ObservableCollection<string>() { "Doing something today", "Doing something else today" } });
Days.Add(new Day() { Date = new DateTime(2014, 5, 2), Tasks = new ObservableCollection<string>() { "Doing nothing today" } });
Days.Add(new Day() { Date = new DateTime(2014, 5, 3), Tasks = new ObservableCollection<string>() { "Doing something today" } });
I'll leave the finer details to you.
Let's assume you have a View Model object called AppointmentViewModel which has all of the tasks that are scheduled to be done on that day. You could then create a View Model class for one row called a WeekViewModel, for example. That then could have one property for each day:
public class WeekViewModel : ViewModelBase {
public AppointmentViewModel Sunday { get { . . . } set { . . . } }
public AppointmentViewModel Monday { get { . . . } set { . . . } }
// and so on for the rest of the week
}
Then, you would have a View Model object for the form with the DataGrid control in it. This would have a property that is of type WeekViewModel. Let's call the collection property Appointments.
The XAML for the DataGrid would look something like this:
<DataGrid AutoGenerateColumns="False"
ItemsSource="{Binding Path=Appointments}"
Name="ThisMonthsAppointmentsGrid">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=Sunday}" />
<DataGridTextColumn Binding="{Binding Path=Monday}" />
<!-- Other DataGridTextColumn definitions for other days of the week -->
</DataGrid.Columns>
</DataGrid>