How to use the same view multiple times in WPF? [duplicate] - c#

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>

Related

Data template parameter

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.

XAML / C# Extracting data from a group of TextBox in a ListView?

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

can't use integer at timer interval

I'm using WPF and the timer doesn't allow to use int for interval. Instead, it asks for TimeSpan
timer1.Interval = TimeSpan.FromMilliseconds(Convert.ToDouble(comboBox1.SelectedItem));
So I changed my code to this but at runtime it gives me an InvalidCastException, saying that the object cannot be converted from System.Windows.Controls.ComboboxItem to System.IConvertible.
How can I solve this?
You should use this
Convert.ToDouble(comboBox1.SelectedText)
The comboBox1.SelectedItem corresponds to the selected item of the ComboBox control and not with the text of it, which is that you want.
Specifically, the SelectedText property of a CombBox control
Gets or sets the text that is selected in the editable portion of a ComboBox.
as it is stated here.
Update
Please use this one:
((ComboBoxItem)comboBox1.SelectedItem).Content.ToString();
Or in two steps:
ComboBoxItem item = (ComboBoxItem)comboBox1.SelectedItem;
timer1.Interval = TimeSpan.FromMilliseconds(Convert.ToDouble(item.Content.ToString()));
For more information about the ComboBoxItem class, please have a look here.
It appears that you are adding ComboBoxItems directly to your ComboBox.
A cleaner and safer approach than parsing strings would be to continue binding to SelectedItem, but to also bind the ItemsSource to a collection of integers.
Then use the ItemTemplate property of the ComboBox to define how to render the integers if you are not satisfied with the default ToString() rendering.
<ComboBox ItemsSource="{Binding Intervals}" SelectedItem="{SelectedInterval}">
<ComboBox.ItemTemplate>
<DataTemplate TargetType="{x:Type Int64}">
<TextBlock Text="{Binding}" Background="Red"/>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox>
With properties looking something like this:
public int SelectedInterval {get;set;}
public List<int> Intervals {
get {
var lst = new List<int>();
for(var i = 1000; i <= 10000; i += 500)
{
lst.Add(i);
}
return lst;
}
}
Now you have strongly type properties that you can manipulate without parsing.

DataGrid - a clean way to display unrelated cell values in a row?

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>

Give element name programmatically

I'm trying to each element I add a unique name which I get from the loop. How can I accomplish it? The line 2 is where is the error.
foreach (var station in stations) {
TextBlock station.name = new TextBlock(); // error !!!
}
Try this instead...
TextBlock station = new TextBlock() { Name="Something" };
Maybe I'm misunderstanding what you want to do... Are you trying to create controls for each member of a collection? If that's what you're doing, try looking at a ListBox, or a more generic ItemsPresenter control, and the ItemTemplate property of that element.
For example, add a ListBox to your XAML, and assign stations as it's ItemsSource, then add a DataTemplate to represent the items.
<ListBox ItemsSource="{Binding stations}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Your trying to create an new instance of textblock on the name property of the textblock instance station.
That is definitely not going to work.
Try:
TextBlock station = new TextBlock();
station.name = "Whatever name you want";

Categories

Resources