I've looked at the answers to various questions, but haven't managed to map the content in the answers to the problem I'm attempting to solve. I've reduced it down to the following code (representative of the outcome I'm trying to achieve), and basically want to be able to render the Person.TitleId as its corresponding Title.TitleText when the row isn't being edited, and have the drop-down bound correctly so that it displays the TitleTexts in the drop-down and writes the associated TitleId back to the Person record when its changed.
In short, what do I put in my <DataGridComboBoxColumn> to achieve this?
App.xaml.cs
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var viewModel = new ViewModels.MainWindowViewModel();
var mainWindow = new MainWindow();
mainWindow.DataContext = viewModel;
mainWindow.ShowDialog();
}
MainWindow.xaml
<Grid>
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Path=Contacts}">
<DataGrid.Columns>
<DataGridComboBoxColumn Header="Title" SelectedItemBinding="{Binding Person}">
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Titles}"/>
<Setter Property="IsReadOnly" Value="True"/>
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Titles}"/>
<Setter Property="DisplayMemberPath" Value="TitleText" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
Person.cs
public class Person
{
public int TitleId { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
}
Title.cs
public struct Title
{
public Title(int titleId, string titleText)
: this()
{
TitleId = titleId;
TitleText = titleText;
}
public string TitleText { get; private set; }
public int TitleId { get; private set; }
public static List<Title> GetAvailableTitles()
{
var titles = new List<Title>();
titles.Add(new Title(1, "Mr"));
titles.Add(new Title(2, "Miss"));
titles.Add(new Title(3, "Mrs"));
return titles;
}
}
MainWindowViewModel.cs
public class MainWindowViewModel : ViewModelBase
{
private ObservableCollection<Person> contacts;
private List<Title> titles;
public MainWindowViewModel()
{
titles = Title.GetAvailableTitles();
Contacts = new ObservableCollection<Person>();
Contacts.Add(new Person() { FirstName = "Jane", LastName = "Smith", TitleId = 2 });
}
public List<Title> Titles
{
get { return titles; }
}
public ObservableCollection<Person> Contacts
{
get { return contacts; }
set
{
if (contacts != value)
{
contacts = value;
this.OnPropertyChanged("Contacts");
}
}
}
}
ViewModelBase.cs
public class ViewModelBase : INotifyPropertyChanged
{
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Here is a working code. The key point here was to use SelectedValueBinding instead of SelecteItemBinding.
<DataGridComboBoxColumn Header="Title"
SelectedValueBinding="{Binding TitleId}"
SelectedValuePath="TitleId"
DisplayMemberPath="TitleText"
>
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Titles}"/>
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Titles}"/>
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
#SnowBear's answer worked well for me. But I want to clarify a detail of the binding.
In #Rob's example, both Title and Person classes use TitleID.
Therefore, in #SnowBear's answer, in the binding:
SelectedValueBinding="{Binding TitleId}"
it wasn't immediately obvious to me which class and property was being bound.
Because the SelectedValueBinding attribute appeared on the DataGridComboBoxColumn, it is binding to the ItemsSource of the containing DataGrid. In this case the Contacts collection of Person objects.
In my case, the DataGrid's DataSource collection was attributed with a property that was named different from the ValuePath of the ComboBox's ItemSource collection. So my SelectedValueBinding's value was bound to a different property than the property named in the ComboBox's SelectedValuePath.
Related
I'm new to MVVM. I have three textboxes and a button in my view. I want that button to be enabled when all those three textboxes are filled. My view is as below:
<StackPanel Margin="1,1,1,1" Grid.Row="0">
<!--<Label Margin="2,2,2,2" Content="ID:"/>
<dxe:TextEdit Margin="2,2,2,2" Text="{Binding ElementName=StudentGrid, Path=SelectedItem.Id}"/>-->
<Label Margin="2,2,2,2" Content="Name:"/>
<dxe:TextEdit Margin="2,2,2,2" x:Name="Name" Text="{Binding Path=Name}" />
<Label Margin="2,2,2,2" Content="Last Name:"/>
<dxe:TextEdit Margin="2,2,2,2" x:Name="LastName" Text="{Binding Path=LastName}" />
<Label Margin="2,2,2,2" Content="Age:"/>
<dxe:TextEdit Margin="2,2,2,2" x:Name="Age" Text="{Binding Path=Age}" />
</StackPanel>
<ListView Name="StudentGrid" Grid.Row="1" Margin="1,1,1,1" ItemsSource="{Binding studentList}">
<ListView.View>
<GridView>
<GridViewColumn Header="ID" Width="50" DisplayMemberBinding="{DXBinding Id}"/>
<GridViewColumn Header="Name" Width="80" DisplayMemberBinding="{DXBinding Name}"/>
<GridViewColumn Header="Last Name" Width="80" DisplayMemberBinding="{DXBinding LastName}"/>
<GridViewColumn Header="Age" Width="50" DisplayMemberBinding="{DXBinding Age}"/>
</GridView>
</ListView.View>
</ListView>
<StackPanel Grid.Row="2" Margin="1,2,1,1">
<dx:SimpleButton x:Name="applybtn" Content="Insert" Width="60" HorizontalAlignment="Left" Margin="5,0,0,0" Command="{Binding Path=_myCommand}"/>
</StackPanel>
InserCommand code is:
public class InsertCommand : ICommand
{
public StudentListViewModel _viewModel { get; set; }
public InsertCommand(StudentListViewModel viewModel)
{
_viewModel = viewModel;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_viewModel.InsertStudent();
}
}
StudentListViewModel code is:
public class StudentListViewModel : INotifyPropertyChanged
{
private string _name;
private string _lastName;
private int _age;
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged("Name");
}
}
public string LastName
{ get => _lastName;
set
{
_lastName = value;
OnPropertyChanged("LastName");
}
}
public int Age
{ get => _age;
set
{
_age = value;
OnPropertyChanged("Age");
}
}
public InsertCommand _myCommand { get; set; }
private ObservableCollection<Student> _studentList;
public StudentListViewModel()
{
_myCommand = new InsertCommand(this);
using (MyContext context = new MyContext())
{
_studentList = new ObservableCollection<Student>(context.Students.ToList());
};
}
public void InsertStudent()
{
Student st = new Student()
{
Name = _name,
LastName = _lastName,
Age = _age
};
using (var context = new MyContext())
{
context.Add(st);
context.SaveChanges();
_studentList.Add(st); //For Getting instatnt UI update
}
}
public ObservableCollection<Student> studentList
{
get
{
return _studentList;
}
set
{
_studentList = value;
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
It's not usual to create a new class for each command.
Instead, create a generic command class with action / func parameters to specify each command's logic.
public class RelayCommand : ICommand
{
private readonly Action _execute;
private readonly Func<bool> _canExecute;
public RelayCommand(Action execute) : this(execute, () => true)
{
}
public RelayCommand(Action execute, Func<bool> canExecute)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
public bool CanExecute(object parameter) => _canExecute.Invoke();
public void Execute(object parameter) => _execute.Invoke();
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
Then, create an instance of this class in your ViewModel that you can bind your button to.
public class StudentListViewModel : INotifyPropertyChanged
{
private string _name;
private string _lastName;
private int _age;
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged("Name");
InsertCommand.RaiseCanExecuteChanged();
}
}
public string LastName
{ get => _lastName;
set
{
_lastName = value;
OnPropertyChanged("LastName");
InsertCommand.RaiseCanExecuteChanged();
}
}
public int Age
{ get => _age;
set
{
_age = value;
OnPropertyChanged("Age");
InsertCommand.RaiseCanExecuteChanged();
}
}
public RelayCommand InsertCommand { get; }
public StudentListViewModel()
{
InsertCommand = new RelayCommand(() => InsertStudent(),
() => !string.IsNullOrEmpty(Name) && !string.IsNullOrEmpty(LastName) && Age > 0);
...
}
public void InsertStudent()
{
....
}
}
For a cleaner way to handle command refresh, so that each property doesn't need to care how it is used, check out my blog post.
Usually I create Binding Validations so that I can reuse on other textboxes.
Then on the button I create triggers to enable or disable the button based on the validations defined.
<Button Comman="{Binding InsertCommand}" Content="Insert">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="IsEnabled" Value="False" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=Name, Path=(Validation.HasError)}" Value="False"/>
<Condition Binding="{Binding ElementName=LastName, Path=(Validation.HasError)}" Value="False"/>
<Condition Binding="{Binding ElementName=Age, Path=(Validation.HasError)}" Value="False"/>
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="True" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
For very simple not null validations, Instead of creating validations, I just test against the text property of the textbox
<Button Comman="{Binding InsertCommand}" Content="Insert">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="IsEnabled" Value="True" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=Name, Path=Text}" Value=""/>
<Condition Binding="{Binding ElementName=LastName, Path=Text}" Value=""/>
<Condition Binding="{Binding ElementName=Age, Path=Text}" Value=""/>
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="False" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
button to be enabled when all those three textboxes are filled.
Create a new bool property called IsButtonEnabled.
Whenever text changes (two-way binding and update source triggeer in the binding right? -> How-to bind, my answer) to push the current string(s) to the VM's properties in real time.
Those properties in question, their values will be checked in the IsButtonEnabled getter. Then in each of the setters for the strings, add a OnPropertyChanged for IsButtonEnabled. Also bind IsButtonEnabled to the proper button property to achieve your desired affect.
Example
// Doesn't need its own OnPropertyChanged, because that is set
// for every keystroke.
public bool IsButtonEnabled { get { return !string.IsNullOrEmpty(Name) &&
!string.IsNullOrEmpty(LastName) &&
!string.IsNullOrEmpty(Age); }}
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged("Name");
OnPropertyChanged("IsButtonEnabled");
InsertCommand.RaiseCanExecuteChanged();
}
}
public string LastName
...
set
{
_lastName = value;
OnPropertyChanged("LastName");
OnPropertyChanged("IsButtonEnabled");
...
public string Age
...
set
{
_age= value;
OnPropertyChanged("Age");
OnPropertyChanged("IsButtonEnabled");
....
Xaml
<dx:SimpleButton x:Name="applybtn" IsEnabled="{Binding IsButtonEnabled}" ...
Similar answer given
DataTemplate.DataTrigger to check for greater than or less than?
WPF Multi-Binding / Aggregate Binding to
Collection
I need to present a WPF GridView where one column is a Combobox, The user can select one value from the list or enter a new value so I set the IsComboBoxEditable to true but the problem is that if the user types a value that is not in the ItemsSource the Text is blank when the Combobox looses the focus.
Note : I don't want, when a new value is typed , this value to be
added to the ItemsSource. I only need to save it's string value in row
that bounded to it.
I also need DropDownOpened event, to populate it's ItemsSource.
Here is my code:
<telerik:GridViewDataColumn Header="Description">
<telerik:GridViewDataColumn.CellTemplate>
<DataTemplate>
<telerik:RadComboBox IsEditable="True" ItemsSource="{Binding Descriptions}" Text="{Binding Description1,Mode=TwoWay}" DropDownOpened="descriptionRadComboBox_DropDownOpened"/>
</DataTemplate>
</telerik:GridViewDataColumn.CellTemplate>
</telerik:GridViewDataColumn>
Description1 is string property, and Descriptions is List of string that populate in runtime.(When DropDownOpened Event occurred)
Like you mentioned, your goal is, simply, to "Editable ComboBox".
(And, of course, you don't want to add new Item to ItemsSource)
<telerik:GridViewDataColumn UniqueName="description1" Header="Description">
<telerik:GridViewDataColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Description1}"></TextBlock>
</DataTemplate>
</telerik:GridViewDataColumn.CellTemplate>
<telerik:GridViewDataColumn.CellEditTemplate>
<DataTemplate>
<telerik:RadComboBox Name="SLStandardDescriptionsRadComboBox" IsEditable="True"
ItemsSource="{Binding DataContext.SLStandardDescriptions, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
DisplayMemberPath="SLStandardDescriptionTitle" DropDownOpened="Description_DropDownOpened">
</telerik:RadComboBox>
</DataTemplate>
</telerik:GridViewDataColumn.CellEditTemplate>
</telerik:GridViewDataColumn>
Codebehinde :
private void RadGridView_CellEditEnded(object sender, GridViewCellEditEndedEventArgs e)
{
if (e.Cell.Column.UniqueName == "description1")
{
RadComboBox combo = e.Cell.ChildrenOfType<RadComboBox>().FirstOrDefault();
if (combo != null)
{
List<Description> comboItems = combo.ItemsSource as List<Description>;
string textEntered = e.Cell.ChildrenOfType<RadComboBox>().First().Text;
bool result = comboItems.Contains(comboItems.Where(x => x.DescriptionTitle == textEntered).FirstOrDefault());
if (!result)
{
comboItems.Add(new Description { DescriptionTitle = textEntered });
combo.SelectedItem = new Description { DescriptionTitle = textEntered };
}
if (_viewModel.AccDocumentItem != null)
{
if (e.Cell.Column.UniqueName == "description1")
_viewModel.AccDocumentItem.Description1 = textEntered;
}
}
}
}
Here is the solution for .net DataGrid control:
<DataGrid ItemsSource="{Binding Path=Items}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=Title}" ></DataGridTextColumn>
<DataGridComboBoxColumn SelectedValueBinding="{Binding ComboItem.ID}" DisplayMemberPath="ComboTitle" SelectedValuePath="ID">
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding Path=DataContext.ComboItems, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding Path=DataContext.ComboItems, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
<Setter Property="IsEditable" Value="True" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
</DataGrid.Columns>
</DataGrid>
Surely you can do this using Telerik DataGrid control as well.
And here is my ViewModel:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
ComboItems = new ObservableCollection<ComboItem>()
{
new ComboItem(){ID=1,ComboTitle="ComboItem1"},
new ComboItem(){ID=2,ComboTitle="ComboItem2"},
new ComboItem(){ID=3,ComboTitle="ComboItem3"}
};
Items = new ObservableCollection<Item>()
{
new Item(){ID=1,Title="Item1",ComboItem=ComboItems[0]},
new Item(){ID=2,Title="Item2",ComboItem=ComboItems[1]},
new Item(){ID=3,Title="Item3",ComboItem=ComboItems[2]}
};
}
public ObservableCollection<Item> Items { get; set; }
public ObservableCollection<ComboItem> ComboItems { get; set; }
}
public class Item
{
public int ID { get; set; }
public string Title { get; set; }
public ComboItem ComboItem { get; set; }
}
public class ComboItem
{
public int ID { get; set; }
public string ComboTitle { get; set; }
}
I'm relatively new to WPF and am having a hard time binding a custom class to a datagrid. While the text properties are OK (they are read-only anyway), the togglebuttons for the boolean properties are not updated in my item list, and they are also not displayed according to the values set initially. They do however respond correctly to clicks in the UI.
<Style x:Key="ToggleImageStyleBien" TargetType="ToggleButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<StackPanel>
<Image Name="img" Source="Images/transp.png"/>
</StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="ToggleButton.IsChecked" Value="True">
<Setter TargetName="img" Property="Source" Value="Images/good.png"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Here's the DataGrid itself:
<DataGrid AutoGenerateColumns="False" Height="Auto" Name="dataGridRevision" Width="Auto" Margin="6,6,6,0" ItemsSource="{Binding}" VerticalScrollBarVisibility="Auto" VerticalGridLinesBrush="{x:Null}">
<DataGrid.Columns>
<DataGridTextColumn Header="Code" Binding="{Binding Code}" Visibility="Collapsed"/>
<DataGridTextColumn Header="DescripciĆ³n" Binding="{Binding Description}" IsReadOnly="True"/>
<DataGridTemplateColumn Header="Bien">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ToggleButton Style="{StaticResource ToggleImageStyleBien}" Click="ToggleButton_Click" IsChecked="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=ReviewItem.Good, Mode=TwoWay}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Comentario" />
</DataGrid.Columns>
</DataGrid>
This is the class I'm binding to:
public class ReviewItem
{
public string Code { set; get; }
public string Description { set; get; }
public bool Good { set; get; }
public bool Bad { set; get; }
public string Comment { set; get; }
}
As far as I can tell, I'm not using the right Binding property in the ToggleButton, but I have tried a lot and have run out of ideas. The list properties are not changing on click, the values are not displayed according to the data.
Please help...
Thanks!
Joerg.
Changed the class to this, based on other examples found:
public class ReviewItem : INotifyPropertyChanged
{
public string Code { set; get; }
public string Description { set; get; }
public bool Bad { set; get; }
public string Comment { set; get; }
private bool _isChecked;
public bool Good
{
get { return _isChecked; }
set
{
System.Diagnostics.Debug.WriteLine("Good = " + value);
_isChecked = value;
OnPropertyChanged("Good");
}
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
You are currently binding to UserControl.ReviewItem, which is not a valid property on a UserControl.
Try binding to DataContext.ReviewItem instead which will bind to UserControl.DataContext.ReviewItem
If you do not mean to bind to UserControl.DataContext.ReviewItem.Good, and instead want to bind to DataGrid.ReviewItems[x].Good, then you only need to bind IsChecked="{Binding Good}". This is because the default DataContext on a DataGridCell is the row's data item, so if your collection contains a list of ReviewItems, the DataContext of the cell is already set to a ReviewItem
Also, I would highly recommend using Snoop for debugging WPF bindings. You can use it on your application to see what the DataContext is for individual UI Elements, and figure out if your bindings are correct or not.
I have an external 'current' property (int) which represents the current index of a collection. I have a listview that displays this collection. I want to be able to style the 'nth' item of the collection depending on the value of 'current', i.e. if current is 3, highlight the 4th item in the collection (index = 3), etc. How can I do this?
An alternative would be to bind when the item text is equal to another external property.
To customize the style of a ListView, you can create a DataTrigger that binds to a property of the View's DataContext to alter the current style. In this example the code alters the Background.
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Background" Value="Aqua"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=DataContext.StyleType, RelativeSource={RelativeSource Self}}" Value="1">
<Setter Property="Background" Value="Chartreuse"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
I've added most of the code here so you can get as complete picture as possible of what is happening, but I skipped the common MVVM base classes.
How it works:
ListView ItemsSource binds to Customers
ListView SelectedItem binds to Customer
ListView.ItemContainerStyle has a DataTrigger that binds to DataContext.StyleType
DataContext is a List of Customer objects with a user defined StyleType property that gets initialized in the code-behind
A Button Command clears the value of StyleType
Clicking a row changes the style of the next row
Customer implements INotifyPropertyChanged, that fires when StyleType changes
DataTrigger alters the Background each ListViewItem
Here is the XAML, look at DataTrigger:
<Window x:Class="ListViewScrollPosition.Views.ScrollBarwindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Style on Model" Height="300" Width="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListView
Grid.Row="0"
SelectedItem="{Binding Customer}"
ItemsSource="{Binding Customers}" x:Name="myListView">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Background" Value="Aqua"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=DataContext.StyleType, RelativeSource={RelativeSource Self}}" Value="1">
<Setter Property="Background" Value="Pink"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView>
<GridViewColumn Header="First Name"
DisplayMemberBinding="{Binding FirstName}" />
<GridViewColumn Header="Last Name"
DisplayMemberBinding="{Binding LastName}" />
<GridViewColumn Header="Style Type"
DisplayMemberBinding="{Binding StyleType}" />
</GridView>
</ListView.View>
</ListView>
<Button Grid.Row="1" Content="Change Style" Command="{Binding Path=AlterStyle}"/>
</Grid>
</Window>
Here is the ViewModel the View uses to get Customers and to alter the value of StyleType:
using System.Collections.Generic;
using System.Windows.Input;
using ListViewScrollPosition.Commands;
using ListViewScrollPosition.Models;
namespace ListViewScrollPosition.ViewModels
{
public class MainViewModel : ViewModelBase
{
private DelegateCommand _alterStyleCommand;
public MainViewModel()
{
}
public ICommand AlterStyle
{
get
{
if (_alterStyleCommand == null)
{
_alterStyleCommand = new DelegateCommand(AlterStyleCommand);
}
return _alterStyleCommand;
}
}
private void AlterStyleCommand()
{
foreach (var customer in Customers)
{
customer.StyleType = 0;
}
}
private void ApplyStyleToNextRow(Customer currentCustomer)
{
bool setNext = false;
foreach (var customer in Customers)
{
if (setNext)
{
customer.StyleType = 1;
setNext = false;
}
else
{
customer.StyleType = 0;
}
if (currentCustomer == customer)
{
setNext = true;
}
}
}
private List<Customer> _customers = Customer.GetSampleCustomerList();
public List<Customer> Customers
{
get
{
return _customers;
}
}
private Customer _customer = null;
public Customer Customer
{
get
{
return _customer;
}
set
{
_customer = value;
ApplyStyleToNextRow(_customer);
OnPropertyChanged("Customer");
}
}
}
}
Here is the Model, ViewModelBase implements INotifyPropertyChanged, StyleType fires OnPropertyChanged when changed which updates each ListViewItem:
using System;
using System.Collections.Generic;
namespace ListViewScrollPosition.Models
{
public class Customer : ViewModels.ViewModelBase
{
public String FirstName { get; set; }
public String LastName { get; set; }
private int _style;
public int StyleType
{
get { return _style;}
set
{
_style = value;
OnPropertyChanged("StyleType");
}
}
public Customer(String firstName, String lastName, int styleType)
{
this.FirstName = firstName;
this.LastName = lastName;
this.StyleType = styleType;
}
public static List<Customer> GetSampleCustomerList()
{
return new List<Customer>(new Customer[4] {
new Customer("A.", "Zero", 0),
new Customer("B.", "One", 1),
new Customer("C.", "Two", 2),
new Customer("D.", "Three", 1)
});
}
}
}
Here is the code-behind where I setup the DataContext:
using System.Windows;
namespace ListViewScrollPosition.Views
{
public partial class ScrollBarwindow : Window
{
public ScrollBarwindow()
{
InitializeComponent();
DataContext = new ViewModels.MainViewModel();
}
}
}
I'd like to build an MRU menu that has the following structure:
File
+=>Recent Files
+=> Doc1.txt
+=> Doc2.txt
-separator-
+=> Clear entries
This being MVVM, my mru list is databound to the View Model. Because I want to add the separator, and I don't fancy inserting the separator and the clear entry action in the list of items, I'm currently using an itemscontrol container for my menu, but I've got horrible padding issues. Do you have any solution that would allow me to just add MenuItem instances ?
Here's the XAML :
<!-- MRU list -->
<MenuItem Header="_Recent Files" >
<ItemsControl ItemsSource="{Binding MostRecentlyUsed.Entries,Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<MenuItem Header="{Binding ShortName}" ToolTip="{Binding FileName}" Command="{Binding OpenCommand}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Separator />
<MenuItem Header="_Clean Entries" Command="{Binding MostRecentlyUsed.CleanCommand}" />
</MenuItem>
<Separator />
Cheers,
Florian
Replace the DataTemplate with an ItemContainerStyle as follows:
<MenuItem Header="_Recent Files" ItemsSource="{Binding Commands,Mode=OneWay}">
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Header" Value="{Binding Path=ShortName}" />
<Setter Property="ToolTip" Value="{Binding Path=FileName}" />
<Setter Property="Command" Value="{Binding Path=OpenCommand}" />
<Setter Property="CommandParameter" Value="{Binding Path=OpenParameter}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsSeparator}" Value="true">
<Setter Property="MenuItem.Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type MenuItem}">
<Separator Style="{DynamicResource {x:Static MenuItem.SeparatorStyleKey}}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
Here is the code I am binding to that is similar to your code (but different):
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
GoCommand = new DelegateCommand<object>(OnGoCommand, CanGoCommand);
LoadCommands();
}
private List<MyCommand> _commands = new List<MyCommand>();
public List<MyCommand> Commands
{
get { return _commands; }
}
private void LoadCommands()
{
MyCommand c1 = new MyCommand { OpenCommand = GoCommand, OpenParameter = "1", ShortName = "Doc1", FileName = "Doc1.txt", IsSeparator = false };
MyCommand c2 = new MyCommand { OpenCommand = GoCommand, OpenParameter = "2", ShortName = "Doc2", FileName = "Doc1.txt", IsSeparator = false };
MyCommand c3 = new MyCommand { OpenCommand = GoCommand, OpenParameter = "4", ShortName = "", FileName = "", IsSeparator = true };
MyCommand c4 = new MyCommand { OpenCommand = GoCommand, OpenParameter = "5", ShortName = "_Clean Entries", FileName = "Clean Entries", IsSeparator = false };
_commands.Add(c1);
_commands.Add(c2);
_commands.Add(c3);
_commands.Add(c4);
}
public ICommand GoCommand { get; private set; }
private void OnGoCommand(object obj)
{
}
private bool CanGoCommand(object obj)
{
return true;
}
}
public class MyCommand
{
public ICommand OpenCommand { get; set; }
public string ShortName { get; set; }
public string FileName { get; set; }
public string OpenParameter { get; set; }
public bool IsSeparator { get; set; }
}