Header inside itemscontrol - wpf

I have a table which contains food types. Each food type has 12 rows per person.
What I need to do is after selecting a person, an itemscontrol will show the 12 rows for each food type.
I have been successful up to this point, but what I would like is a heading above each 12 rows stating the food type, without repeating it on each row.
Does anyone know a way to accomplish this?
My only way so far is to put a headereditemscontrol inside an itemscontrol. This seems to be a very complicated way to such a simple issue.
Thanks in advance for your help.

Try:
xaml:
<StackPanel Grid.Row="1">
<TextBlock Text="{Binding Path=FoodTypes[0].Description}" />
<ItemsControl ItemsSource="{Binding Path=FoodTypes}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Margin="15,0,0,0" Text="{Binding Path=Text}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MyViewModel();
}
}
public class FoodType
{
public string Description {get;set;}
public string Text {get;set;}
}
class MyViewModel : INotifyPropertyChanged
{
private ObservableCollection<FoodType> foodTypes;
public ObservableCollection<FoodType> FoodTypes
{
get { return this.foodTypes; }
set
{
this.foodTypes = value;
this.OnPropertyChanged("FoodTypes");
}
}
public MyViewModel()
{
this.FoodTypes = new ObservableCollection<FoodType>();
this.FoodTypes.Add(new FoodType() { Description = "Test1", Text = "Something" });
this.FoodTypes.Add(new FoodType() { Description = "Test1", Text = "Something Else" });
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if(handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}

Related

Using bound data in DataTemplate

I have a CustomWindow.cs that I'm decorating using a DataTemplate, as there are a large number of content variations. As per MVVM, the window's DataContext is bound to a ViewModel
Ideally, some of these decorations would be populated using data from the ViewModel.
The structure I would like to achieve is something like the following:
<CustomWindow DataContext="{Binding Main, Source={StaticResource Locator}}">
<Content>
</CustomWindow>
The DataTemplate may look something like:
<DataTemplate DataType="{x:Type CustomWindow}">
<ContentPresenter Content="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type viewModels:HmiViewModelBase}}}">
<ContentPresenter Content="{Binding Content}"/>
</ContentPresenter>
</DataTemplate>
I realise that the double definition of Content in ContentPresenter wouldn't work but can't think of an alternative.
How would I achieve something like this?
I feel like this would be a common issue.
first of all welcome to SO. Please look at the next concept: 1) One main view model contains a number of models in some observable collection (or based on binding and properties). 2) Each model in observable collection has its own logic and is supposed to be presented in some original way. 3) The main view model is presented by main view (let say list box).
4) Each mode inside the observable collection of the main view model is presented by content control which will select some original content template for its content (which is a model inside the observable collection). 5)Data template based on the model type can use every wpf control (or user custom control you made) and present data. Here is the code:
1. XAML code:
<Window x:Class="DataTemplateSOHelpAttempt.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dataTemplateSoHelpAttempt="clr-namespace:DataTemplateSOHelpAttempt"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<dataTemplateSoHelpAttempt:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type dataTemplateSoHelpAttempt:OrangeObject}">
<TextBlock Text="{Binding Description}" Background="Orange"></TextBlock>
</DataTemplate>
<DataTemplate DataType="{x:Type dataTemplateSoHelpAttempt:GreenObject}">
<TextBlock Text="{Binding Description}" Background="GreenYellow"></TextBlock>
</DataTemplate>
<DataTemplate DataType="{x:Type dataTemplateSoHelpAttempt:BlueObject}">
<TextBlock Text="{Binding Description}" Background="CadetBlue"></TextBlock>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding Objects}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<ContentControl Content="{Binding }"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Grid></Window>
2. View model code:
public class MainViewModel:BaseObservableObject
{
public MainViewModel()
{
Objects = new ObservableCollection<BaseDataObject>(new List<BaseDataObject>
{
new BlueObject{Description = "Hello I'm blue object!!!"},
new OrangeObject{Description = "Hello I'm orange object!!!"},
new GreenObject{Description = "Hello I'm green object!!!"},
new OrangeObject{Description = "Hello I'm anoter orange object!!!"},
new BlueObject{Description = "Hello I'm another blue object!!!"},
new OrangeObject{Description = "Hello I'm another orange again object!!!"},
new GreenObject{Description = "Hello I'm another green object!!!"},
new OrangeObject{Description = "Hello I'm again another orange object!!!"},
});
}
public ObservableCollection<BaseDataObject> Objects { get; set; }
}
3. Models Code:
public abstract class BaseDataObject:BaseObservableObject
{
public abstract string Description { get; set; }
}
public class OrangeObject:BaseDataObject
{
private string _description;
public override string Description
{
get { return _description; }
set
{
_description = value;
OnPropertyChanged();
}
}
}
public class BlueObject:BaseDataObject
{
private string _description;
public override string Description
{
get { return _description; }
set
{
_description = value;
OnPropertyChanged();
}
}
}
public class GreenObject:BaseDataObject
{
private string _description;
public override string Description
{
get { return _description; }
set
{
_description = value;
OnPropertyChanged();
}
}
}
4. Basic INotifyPropertyChanged implementation (use 4.5 dotNet version for CallerMemberName):
public class BaseObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> raiser)
{
var propName = ((MemberExpression)raiser.Body).Member.Name;
OnPropertyChanged(propName);
}
protected bool Set<T>(ref T field, T value, [CallerMemberName] string name = null)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
OnPropertyChanged(name);
return true;
}
return false;
}
}
That's all, copy/past, debug and use. I'll be glad to help if you will have problems with the code. Please mark it as answered if the answer was helpful.
Regards,

ListBox SelectedItem Binding Broken

I am using WPF having a strange issue with RadListBox SelectedItem databinding, trying to figure out but no luck. Following is my scenario
I am using Telerik Controls (RadListBox, and RadButton)
RadButton is placed inside a ItemsControl, RadListBox and ItemsControl are bind to same ItemsSource.
I am using PRISM and MVVM.
What I want is when I click on button, the same item is selected from RadListBox automatically, (This part working fine).
Problem: As soon as I click on any item of RadListBox and then click back on any button the item selection stops working.
Edit: I tried the same thing with standard WPF ListBox by adding attached behavior for selection changed event and attached property of Command and CommandParameter, it works fine, so it looks like an issue with Telerik RadListBox ?
Now let me come to code.
ViewModel Class
public class MainViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public DelegateCommand<object> StudentSelected { get; set; }
public DelegateCommand<object> ButtonPressed { get; set; }
private void OnStudentSelected(object par)
{
//Debugger.Break();
if (handled == false)
{
Student std = par as Student;
if (std != null)
{
SelectedStudent = std;
}
}
handled = false;
}
private void OnButtonPressed(object par)
{
//Debugger.Break();
handled = true;
String std = par as String;
if (std != null)
{
foreach (Student st in _students)
{
if (st.Name.Equals(std))
{
SelectedStudent = st;
break;
}
}
}
}
private Student _selectedstudent;
private bool handled = false;
public MainViewModel()
{
StudentSelected = new DelegateCommand<object>(OnStudentSelected);
ButtonPressed = new DelegateCommand<object>(OnButtonPressed);
}
public Student SelectedStudent
{
get
{
return _selectedstudent;
}
set
{
_selectedstudent = value;
OnPropertyChanged("SelectedStudent");
}
}
private ObservableCollection<Student> _students;
public ObservableCollection<Student> Students
{
get
{
return _students;
}
set
{
_students = value;
OnPropertyChanged("Students");
}
}
}
public class Student
{
public String Name { get; set; }
public String School { get; set; }
}
MainView XAML
<telerik:RadListBox Grid.Column="0" Grid.Row="0" ItemsSource="{Binding Students}" Command="{Binding StudentSelected}" CommandParameter="{Binding RelativeSource={RelativeSource Mode=Self}, Path=SelectedItem}" SelectedItem="{Binding SelectedStudent, Converter={StaticResource DebugConverter}}">
<!-- The above debug converter is just for testing binding, as long as I keep on clicking button the Converter is being called, but the moment I click on RadListBoxItem the Converter is not called anymore, even when I click back on buttons -->
<telerik:RadListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"></TextBlock>
</DataTemplate>
</telerik:RadListBox.ItemTemplate>
</telerik:RadListBox>
<Label Grid.Row="0" Grid.Column="1" Content="{Binding SelectedStudent.Name}"></Label>
<StackPanel Grid.Column="1" Grid.Row="1" Orientation="Horizontal">
<ItemsControl ItemsSource="{Binding Students}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<telerik:RadButton Width="100" Height="70" Content="{Binding Name}" Command="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}}, Path=DataContext.ButtonPressed}" CommandParameter="{Binding RelativeSource={RelativeSource Mode=Self}, Path=Content}">
</telerik:RadButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
and finally populating the ViewModel and setting the Datacontext
MainViewModel mvm = new MainViewModel();
ObservableCollection<Student> students = new ObservableCollection<Student>();
students.Add(new Student { Name = "Student 1", School = "Student 1 School" });
students.Add(new Student { Name = "Student 2", School = "Student 2 School" });
students.Add(new Student { Name = "Student 3", School = "Student 3 School" });
mvm.Students = students;
//Bind datacontext
this.DataContext = mvm;
Please give your suggestions and share you expertise from WPF Jargon?
Finally I figured out the issue, I just need to replace the RadListBox SelectedItem binding to TwoWay
<telerik:RadListBox Grid.Column="0" Grid.Row="0" ItemsSource="{Binding Students}" Command="{Binding StudentSelected}" CommandParameter="{Binding RelativeSource={RelativeSource Mode=Self}, Path=SelectedItem}" SelectedItem="{Binding SelectedStudent, Mode,TwoWay, Converter={StaticResource DebugConverter}}">

Binding to a structure

I am trying to bind a listview item to a member of a structure, but I am unable to get it to work.
The structure is quite simple:
public struct DeviceTypeInfo
{
public String deviceName;
public int deviceReferenceID;
};
in my view model I hold a list of these structures and I want to get the "deviceName" to be displayed in a list box.
public class DevicesListViewModel
{
public DevicesListViewModel( )
{
}
public void setListOfAvailableDevices(List<DeviceTypeInfo> devicesList)
{
m_availableDevices = devicesList;
}
public List<DeviceTypeInfo> Devices
{
get { return m_availableDevices; }
}
private List<DeviceTypeInfo> m_availableDevices;
}
I have tried the following but I can't get the binding to work, do I need to use relativesource?
<ListBox Name="DevicesListView" Grid.Column="0" VerticalAlignment="Bottom" HorizontalAlignment="Center" Margin="10" MinHeight="250" MinWidth="150" ItemsSource="{Binding Devices}" Width="Auto">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding DeviceTypeInfo.deviceName}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You need to make the members in the struct properties.
public struct DeviceTypeInfo
{
public String deviceName { get; set; }
public int deviceReferenceID { get; set; }
};
I ran into a similar situation yesterday :P
EDIT: Oh yeah, and like Jesse said, once you turn them into properties, you'll want to set up the INotifyPropertyChanged event.
Your TextBlock's DataContext is an object of type DeviceTypeInfo, so you only need to bind to deviceName, not DeviceTypeInfo.deviceName.
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding deviceName}"/>
</StackPanel>
</DataTemplate>
In addition, you should be binding to Properties, not fields. You can change your fields to properties by adding { get; set; } to them like the other answer suggests
I think you need getters and setters. You also might need to implement INotifyPropertyChanged.
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}

ItemsControl that contains bound ComboBox in ItemTemplate

I've just stuck in a problem to bind collection in ItemsControl with ItemTeplate that contains bounded ComboBox.
In my scenario I need to "generate" form that includes textbox and combobox for each item in collection and let user to update items. I could use DataGrid for that but I'd like to see all rows in edit mode, so I use ItemsControl with custom ItemTemplate.
It's ok to edit textboxes but when you try to change any ComboBox, all other ComboBoxes in other rows will change too.
Is it a bug or feature?
Thanks, Ondrej
Window.xaml
<Window x:Class="ComboInItemsControlSample.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="480" Width="640">
<Window.Resources>
<CollectionViewSource x:Key="cvsComboSource"
Source="{Binding Path=AvailableItemTypes}" />
<DataTemplate x:Key="ItemTemplate">
<Border BorderBrush="Black" BorderThickness="0.5" Margin="2">
<Grid Margin="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="20" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Text="{Binding Path=ItemValue}" />
<ComboBox Grid.Column="2"
SelectedValue="{Binding Path=ItemType}"
ItemsSource="{Binding Source={StaticResource cvsComboSource}}"
DisplayMemberPath="Name"
SelectedValuePath="Value" />
</Grid>
</Border>
</DataTemplate>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding Path=SampleItems}"
ItemTemplate="{StaticResource ItemTemplate}"
Margin="10" />
</Grid>
Window.xaml.cs
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
public class ViewModel
{
public ViewModel()
{
SampleItems = new List<SampleItem> {
new SampleItem { ItemValue = "Value 1" },
new SampleItem { ItemValue = "Value 2" },
new SampleItem { ItemValue = "Value 3" }
};
AvailableItemTypes = new List<SampleItemType> {
new SampleItemType { Name = "Type 1", Value = 1 },
new SampleItemType { Name = "Type 2", Value = 2 },
new SampleItemType { Name = "Type 3", Value = 3 },
new SampleItemType { Name = "Type 4", Value = 4 }
};
}
public IList<SampleItem> SampleItems { get; private set; }
public IList<SampleItemType> AvailableItemTypes { get; private set; }
}
public class SampleItem : ObservableObject
{
private string _itemValue;
private int _itemType;
public string ItemValue
{
get { return _itemValue; }
set { _itemValue = value; RaisePropertyChanged("ItemValue"); }
}
public int ItemType
{
get { return _itemType; }
set { _itemType = value; RaisePropertyChanged("ItemType"); }
}
}
public class SampleItemType : ObservableObject
{
private string _name;
private int _value;
public string Name
{
get { return _name; }
set { _name = value; RaisePropertyChanged("Name"); }
}
public int Value
{
get { return _value; }
set { _value = value; RaisePropertyChanged("Value"); }
}
}
public abstract class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName) {
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Picture
here you can see the result on picture
I believe it's because you're binding to a CollectionViewSource, which tracks the current item. Try binding directly to your list instead, which won't track the current item
<ComboBox Grid.Column="2"
SelectedValue="{Binding Path=ItemType}"
DisplayMemberPath="Name"
SelectedValuePath="Value"
ItemsSource="{Binding RelativeSource={
RelativeSource AncestorType={x:Type ItemsControl}},
Path=DataContext.AvailableItemTypes}" />
While you have a combobox in each row, it doesnt see these comboboxes as being seperate. i.e. They are all using the same collection, and the same selectedValue, so when a value changes in one box, it changes in all of them.
The best way to fix this is to add the SampleItemType collection as a property on your SampleItem model and to then bind the combo box to that property.

How can I bind an ObservableCollection to TextBoxes in a DataTemplate?

I am trying to successfully TwoWay bind an ObservableCollection to TextBoxes in a DataTemplate. I can get the data to display properly, but I am unable to change the list data through the UI. I have a Model class named 'model' which contains an ObservableCollection named 'List'. The class implements the INotifyPropertyChanged interface. Here is the xaml for the shell. The DataContext for Window1's grid is set to "theGrid.DataContext=model"
<Window x:Class="BindThat.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:BindThat"
Title="Window1" Height="300" Width="300">
<StackPanel x:Name="theGrid">
<GroupBox BorderBrush="LightGreen">
<GroupBox.Header>
<TextBlock Text="Group" />
</GroupBox.Header>
<ItemsControl ItemsSource="{Binding Path=List}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=., Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
</StackPanel>
This is the code for the Model class:
class Model : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
private ObservableCollection<string> _list = new ObservableCollection<string>();
public ObservableCollection<string> List
{
get { return _list; }
set
{
_list = value;
NotifyPropertyChanged("List");
}
}
public Model()
{
List.Add("why");
List.Add("not");
List.Add("these?");
}
}
Could anyone advise if I am going about this the correct way?
You need a property to bind two way, so string is not good for this.
Wrap it in a string object, like this:
public class Model
{
public ObservableCollection<StringObject> List { get; private set; }
public Model()
{
List = new ObservableCollection<StringObject>
{
new StringObject {Value = "why"},
new StringObject {Value = "not"},
new StringObject {Value = "these"},
};
}
}
public class StringObject
{
public string Value { get; set; }
}
and bind to Value property instead of "."
Also, you don't need to notify of a change in observable collection, so until your model has some other propertis of its own, it does not need to have INotifyPropertyChange. If you want your ItemsControl react to changes in the individual StringObjects, then you should add INotifyPropertyChanged to a StringObject.
And yet again, two way binding is default, so you need only
<TextBox Text="{Binding Path=Value}" />
in your binding.
I believe you need to derive your collection items from DependencyObject for TwoWay binding to work. Something like:
public class DependencyString: DependencyObject {
public string Value {
get { return (string)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(string), typeof(DependencyString), new UIPropertyMetadata(""));
public override string ToString() {
return Value;
}
public DependencyString(string s) {
this.Value = s;
}
}
public class Model {
private ObservableCollection<DependencyString> _list = new ObservableCollection<DependencyString>();
public ObservableCollection<DependencyString> List {
get { return _list; }
}
public Model() {
List.Add(new DependencyString("why"));
List.Add(new DependencyString("not"));
List.Add(new DependencyString("these?"));
}
}
...
<StackPanel x:Name="theGrid">
<GroupBox BorderBrush="LightGreen">
<GroupBox.Header>
<TextBlock Text="Group" />
</GroupBox.Header>
<ItemsControl ItemsSource="{Binding Path=List}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=Value, Mode=TwoWay}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
</StackPanel>
xaml view:
<ItemsControl ItemsSource="{Binding List}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
in code behind in the constructor:
DataContext = new ViewModel();
in ViewModel Class:
class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
private ObservableCollection<StringObject> _List = new ObservableCollection<StringObject>();
public ObservableCollection<StringObject> List
{
get { return _List; }
set
{
_List = value;
NotifyPropertyChanged("List");
}
}
public ViewModel()
{
List = new ObservableCollection<StringObject>
{
new StringObject {Value = "why"},
new StringObject {Value = "not"},
new StringObject {Value = "these"}
};
}
}
public class StringObject
{
public string Value { get; set; }
}
Be careful with a collection with type string it doesn't work, you have to use an object => StringObject

Resources