I have a listview in my WPF application and the first column is a Checkbox. This checkbox is bound to the IsSelected property of my model and the event propogation happens correctly.
I also have a Checkbox in the same column's header and want to implement a 'Select All' feature where it checks all the listview items.
I'm using pattern MVVM.
The Event doesn't fire!
Can someone explain what I am doing wrong here..
The relevant code portions are mentioned below..
XAML:
<ListView Grid.Row="0"
ItemsSource="{Binding Path=WorkOrders}"
Margin="5,10,5,5"
Name="WorkOrders"
SelectionMode="Multiple"
FontSize="13"
Background="AliceBlue"
BorderBrush="AliceBlue">
<!--Style of items-->
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<!--Properties-->
<Setter Property="Control.HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Control.VerticalContentAlignment" Value="Center" />
<!--Trigger-->
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="{x:Null}" />
<Setter Property="BorderBrush" Value="{x:Null}" />
</Trigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView >
<GridViewColumn CellTemplate="{StaticResource CheckBoxDataTemplate}" Width="80" >
<GridViewColumn.HeaderTemplate>
<DataTemplate>
<CheckBox Command="{Binding Path=SelectAllCommand}" />
</DataTemplate>
</GridViewColumn.HeaderTemplate>
</GridViewColumn>
<GridViewColumn Header="WorkOrder" CellTemplate="{StaticResource DetailIdenTemplate}" Width="300"/>
</GridView>
</ListView.View>
</ListView>
Model:
public class WorkOrder
{
public int CD_WORK_ORDER { get; set; }
public string ID_WORK_ORDER { get; set; }
public bool IsSelected { get; set; }
}
ViewModel:
public class LockWorkOrderSelectionViewModel : ViewModelBase
{
RelayCommand _selectAllCommand;
public ICommand SelectAllCommand
{
get
{
if (_selectAllCommand == null)
{
_selectAllCommand = new RelayCommand(
param => SelectAllElement(),
param => CanSelectAll);
}
//RaiseEvent(new RoutedEventArgs(SearchEvent));
return _selectAllCommand;
}
}
private bool _selectedAllElement;
public bool SelectAllElement()
{
foreach (var item in WorkOrders)
{
item.IsSelected = true;
}
return true;
}
public bool CanSelectAll
{
get { return true; }
}
public List<string> WorkOrdersList
{
get { return _workOrdersList; }
}
private ObservableCollection<WorkOrder> _workOrders = new ObservableCollection<WorkOrder>();
public ObservableCollection<WorkOrder> WorkOrders
{
get
{
int progr = 1;
foreach (var item in WorkOrdersList)
{
if (_workOrders.FirstOrDefault(i => i.ID_WORK_ORDER == item) == null)
{
_workOrders.Add(new WorkOrder { CD_WORK_ORDER = progr, ID_WORK_ORDER = item, IsSelected = false });
progr++;
}
}
return _workOrders;
}
}
}
<CheckBox IsChecked="{Binding DataContext.SelectAll, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" />
Works for me.
Related
I would like to change the picture in the cell when the user clicks on the picture. The initial picture is the "right arrow" and when the user clicks the "right arrow" it should be "red x" but the icon does not change. The value of property (Binding value[3]) changes every time to 0 and 1 so it works fine.
Here is my code:
<GridViewColumn Header="Functions">
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image x:Name="MoveImg" Cursor="Hand" ToolTip="Move Losses" MaxWidth="20" MaxHeight="20" Margin="3,3,3,3" MouseLeftButtonUp="MoveImg_MouseLeftButtonUp" >
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding Value[3]}" Value="1">
<Setter Property="Source" Value="pack://application:,,,/Resources/x.ico"/>
</DataTrigger>
<DataTrigger Binding="{Binding Value[3]}" Value="0">
<Setter Property="Source" Value="pack://application:,,,/Resources/right_arrow.png"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
And the code in DtListView.xaml.cs
Dictionary<string, double[]> prDict = new Dictionary<string, double[]>();
Dictionary<string, string[]> lossTransferDict = new Dictionary<string, string[]>();
public Dictionary<string, double[]> PrDict { get => prDict; set => prDict = value; }
public DtListView()
{
InitializeComponent();
}
private void MoveImg_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (TopLossesLv.SelectedItems != null)
{
foreach (KeyValuePair<string, double[]> item in TopLossesLv.SelectedItems)
{
if (!lossTransferDict.ContainsKey(item.Key))
{
lossTransferDict.Add(item.Key, new string[4] { line, item.Value[0].ToString(), item.Value[1].ToString(), item.Value[2].ToString()});
prDict[item.Key][3] = 1; // Modify Value for Picture change
}
else
{
lossTransferDict.Remove(item.Key);
prDict[item.Key][3] = 0; // Modify Value for Picture change
}
}
}
}
This is not C. This is C#. C# is object oriented. Binding to an array of related values is not a good idea for many reasons. Instead bind to an object, that encapsulates the related values of the array.
Instead of binding to a dictionary of arrays, you should bind to a collection of objects. To do this, you would need to convert the array into a class, which implements INotifyPropertyChanged.
Each field of the array becomes a class property. You should apply this refactoring to all your Binding.Source, where the source is an arrays of values. At the end you should be able to bind to lists only.
Avoid binding controls to dictionaries or arrays.
This is a simple example how to use objects (data models) to populate a ListView and change the GridView cell content (the Image) based on the value of a property SimpleDataObject.IsActive of the data model SimpleDataObject:
SimpleDataObject.cs
class SimpleDataObject : INotifyPropertyChaged
{
// Constructor
public SimpleDataObject(string data) => this.DataValue = data;
private bool isActive;
public bool IsActive
{
get => this.isActive;
set
{
this.isActive = value;
OnPropertyChanged();
}
}
private string dataValue;
public string DataValue
{
get => this.dataValue;
set
{
this.dataValue = value;
OnPropertyChanged();
}
}
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
ViewModel.cs
// Binding source
class ViewModel : INotifyPropertyChaged
{
public ViewModel()
{
this.SimpleDataObjects = new ObservableCollection<SimpleDataObject>()
{
new SimpleDataObject("Data value 1"),
new SimpleDataObject("Data value 2"),
new SimpleDataObject("Data value 3")
}
}
public ObservableCollection<SimpleDataObject> SimpleDataObjects { get; set; }
}
MainWindow.xaml.cs
partial class MainWindow : Window
{
private void OnImageMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
var image = sender as Image;
var dataItem = image.DataContext as SimpleDataObject;
dataItem.IsActive = true; // Toggle image
}
}
MainWindow.xaml
<Window>
<Window.DataContext>
<ViewModel />
</Window.DataContext>
<ListView ItemsSource="{Binding SimpleDataObjects}">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="Functions">
<GridViewColumn.CellTemplate>
<DataTemplate DataType="{x:Type SimpleDataObject}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding DataValue}"/>
<Image MouseLeftButtonUp="OnImageMouseLeftButtonUp">
<Image.Style>
<Style TargetType="Image">
<!-- Default value. Applies when the observed property IsActive returns false -->
<Setter Property="Source" Value="pack://application:,,,/Resources/right_arrow.png" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsActive}" Value="True">
<Setter Property="Source" Value="pack://application:,,,/Resources/x.ico" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
</Window>
I am trying to select all Items in a ListView, using mvvm. Using code-behind, I have tried SelectAll() and foreach to select them, but I would like to use MVVM like the rest of my project. Any ideas?
Here is the listview:
<ListView x:Name="TransformerList" ItemsSource="{Binding CurrentStations}" Margin="16,250,0,10.4" SelectionMode="Multiple" HorizontalAlignment="Left" Width="411">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Select">
<i:InvokeCommandAction Command="{Binding SeeAllCustomersCommand}" CommandParameter="{Binding Item1}"/>
</i:EventTrigger>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding SelectedCustomersChangedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=IsSelected}" />
<Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=TransformerIsSelected}" />
</Style>
</ListView.ItemContainerStyle>
<ListView.View>
<GridView>
<GridViewColumn Header="Nettstasjon" Width="70" DisplayMemberBinding="{Binding Path=Name}" />
<GridViewColumn Header="Område" Width="210" DisplayMemberBinding="{Binding Path=Area}"/>
<GridViewColumn Header="Radial" Width="110" DisplayMemberBinding="{Binding Path=Radial}"/>
</GridView>
</ListView.View>
</ListView>
and the button:
public ICommand cmd_VelgAlle { get { return new RelayCommand(on_cmd_VelgAlle); } }
private void on_cmd_VelgAlle()
{
foreach (item i in CurrentStations) //the item here gives an error "type or namespace could not be found"
{
i.TransformerIsSelected = true;
}
}
And the mvvm:
private bool _TransformerIsSelected;
public bool TransformerIsSelected
{
get { return _TransformerIsSelected; }
set
{
_TransformerIsSelected = value;
RaisePropertyChanged("TransformerIsSelected");
}
}
You need to create a ListViewItem Style:
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</ListView.ItemContainerStyle>
Create a property in Model class:
public bool IsSelected
{
get { return isSelected; }
set
{
isSelected = value;
RaiseChange("IsSelected");
}
}
And iterate your ItemSource to set IsSelected true for all items.(In ViewModel)
foreach(item i in yourCollection)
{
i.IsSelected = true;
}
Update:
Use Style as:
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=TransformerIsSelected}" />
</Style>
</ListView.ItemContainerStyle>
Command:
private void on_cmd_VelgAlle()
{
//Item is the class your CurrentStations is made of(i guess something like `station` in your design)
//i.e if CurrentStations is list of string then Item will be string
foreach (Item i in CurrentStations)
{
i.TransformerIsSelected = true;
}
}
I have 3 columns . One with check box , One with text column and one column with drop down.
I am binding the entire table itemsource to StepTaskViewInfo.CurrentStep.ProjectTasks.Items . StepTaskViewInfo is a variable in my VM and others are nested in it. This works fine .
Only thing that doesnt work is the IsReadOnly property of the FIRST Columns. Am assuming this is some issue because my items source is different and the read only property is different in terms of level of nesting from view model.
For grid :
Items Source = StepTaskViewInfo -> CurrentStep -> ProjectTasks- >Items
For read only propety of each column(which doesnt work) :
IsReadOnly="{Binding StepTaskViewInfo.AreStepsTasksReadonly
StepTaskViewInfo => AreStepsTasksReadonly
<DataGrid RowHeaderWidth="0" x:Name ="TaskDataGrid" Margin="20,0,0,0" ItemsSource="{Binding StepTaskViewInfo.CurrentStep.ProjectTasks.Items}" AutoGenerateColumns="False"
CanUserSortColumns="False" HorizontalAlignment="Left" CanUserAddRows="False" SelectionChanged="TaskRowSelectionChanged"
ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.HorizontalScrollBarVisibility="Auto"
Background="White" BorderThickness ="0"
ScrollViewer.CanContentScroll="True" Height="240">
<DataGrid.Columns>
<DataGridTemplateColumn Width ="60" HeaderStyle="{StaticResource HeaderStyle}" Header="Selected" IsReadOnly="{Binding StepTaskViewInfo.AreStepsTasksReadonly,UpdateSourceTrigger=PropertyChanged }">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox VerticalAlignment="Center" HorizontalAlignment="Center" IsChecked="{Binding IsSelected,UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding IsTaskEnabled,UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
//Column 2
<DataGridTextColumn HeaderStyle="{StaticResource HeaderStyle}" Header="Tasks" Width ="*" Binding="{Binding Name}" IsReadOnly="True">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="TextWrapping" Value="Wrap" />
</Style>
</DataGridTextColumn.ElementStyle>
<DataGridTextColumn.EditingElementStyle>
<Style TargetType="TextBox">
<Setter Property="TextWrapping" Value="Wrap" />
<Setter Property="AcceptsReturn" Value="true" />
</Style>
</DataGridTextColumn.EditingElementStyle>
</DataGridTextColumn>
//Column 3
<DataGridTemplateColumn HeaderStyle="{StaticResource HeaderStyle}" Header="Status" Width ="130">
<DataGridTemplateColumn.HeaderTemplate >
<DataTemplate>
<StackPanel Orientation="Horizontal" Width="130">
<Label Content ="Status" HorizontalAlignment="Left" Margin ="0,0,0,0"/>
<ComboBox Name ="DefaultStatusComboBox" ItemsSource="{StaticResource Status}" Width="86" DropDownClosed="DefaultStatusComboBox_DropDownClosed" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox SelectedItem="{Binding Status, UpdateSourceTrigger=PropertyChanged}" Height ="26" VerticalAlignment="Top" IsReadOnly ="{Binding StatusIsReadOnly}"
IsEnabled ="{Binding IsSelected}" ItemsSource="{StaticResource Status}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
view model:
public class StepTaskViewModel : INavigationAware, INotifyPropertyChanged
{
private readonly IProjectWorkflowService projectWorkflowService;
private bool isVisible = true;
private readonly IUserService userService;
private string stageId;
private StepTaskViewInfo stepTaskViewInfo;
public StepTaskViewModel(IProjectWorkflowService projectWorkflowService, IUserService userService)
{
this.projectWorkflowService = projectWorkflowService;
this.userService = userService;
StepTaskViewInfo=new StepTaskViewInfo();
}
public StepTaskViewInfo StepTaskViewInfo
{
get { return stepTaskViewInfo; }
set
{
stepTaskViewInfo = value;
OnPropertyChanged();
}
}
// set current step - >load tasks - > set display names for each task --> set drop down source for current step
public string StageId
{
get { return stageId; }
set
{
stageId = value;
StepTaskViewInfo.PeerReview.StageId = stageId;
LoadData();
}
}
#region navigation
public void OnNavigatedTo(NavigationContext navigationContext)
{
StageId =(string) navigationContext.Parameters["StageId"] ;
IsVisible = true;
}
public bool IsNavigationTarget(NavigationContext navigationContext)
{
return true;
}
public void OnNavigatedFrom(NavigationContext navigationContext)
{
if (!IsVisible)
return;
IsVisible = false;
}
public bool IsVisible
{
get { return isVisible; }
set
{
isVisible = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "" )
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
// called when stage id changes
public void LoadData()
{
var stepTaskViewInfo = projectWorkflowService.LoadProjectStepTaskInfo(StageId);
if (StepTaskViewInfo.CurrentStep != null)
{
StepTaskViewInfo.CurrentStep.ProjectTasks.Items.Clear();
}
StepTaskViewInfo.AllTeamMembers = stepTaskViewInfo.AllTeamMembers;
StepTaskViewInfo.ProjectSteps = stepTaskViewInfo.ProjectSteps;
StepTaskViewInfo.PeerReview = stepTaskViewInfo.PeerReview;
StepTaskViewInfo.AreStepsTasksReadonly = stepTaskViewInfo.AreStepsTasksReadonly;
StepTaskViewInfo.PeerReview.Documents.Items.Add(new ActivityArtifact { FileName = string.Empty });
}
private string GetAliases(ObservableCollection<SelectableTeamMember> selectedStepMembers)
{
string aliases= selectedStepMembers.Aggregate("", (current, member) => current + (member.Alias + ";"));
aliases= aliases.TrimEnd(';');
return aliases;
}
private string GetDisplayNames(ObservableCollection<SelectableTeamMember> selectedStepMembers)
{
string names = selectedStepMembers.Aggregate("", (current, member) => current + (member.Name + ";"));
names= names.TrimEnd(';');
return names;
}
public void AssignResourcesToStep(ObservableCollection<SelectableTeamMember> selectedStepMembers)
{
StepTaskViewInfo.CurrentStep.StepTeamMembers = selectedStepMembers;
StepTaskViewInfo.CurrentStep.Resources = GetAliases(selectedStepMembers);
StepTaskViewInfo.CurrentStep.StepResourceDisplayName = GetDisplayNames(selectedStepMembers);
foreach (var task in StepTaskViewInfo.CurrentStep.ProjectTasks)
{
task.AllTaskTeamMembers = StepTaskViewInfo.CurrentStep.StepTeamMembers;
task.Resources = GetAliases(StepTaskViewInfo.CurrentStep.StepTeamMembers);
task.TaskResourceDisplayName = GetDisplayNames(StepTaskViewInfo.CurrentStep.StepTeamMembers);
}
}
public void AssignResourcesToTask(ObservableCollection<SelectableTeamMember> selectedTaskMembers, string taskId)
{
var task = StepTaskViewInfo.CurrentStep.ProjectTasks.First(st => st.Id == taskId);
task.Resources = GetAliases(selectedTaskMembers);
task.TaskResourceDisplayName = GetDisplayNames(selectedTaskMembers);
}
public void AssignTaskTips(string ttid)
{
string taskTip = projectWorkflowService.GetTaskTip(ttid);
foreach (var task in StepTaskViewInfo.CurrentStep.ProjectTasks)
{
if (task.TemplateTaskId == ttid)
task.TaskTip = taskTip;
}
}
#region peerreview
public void DownloadDocument(string artifactId, string fileName)
{
projectWorkflowService.DownloadActivityArtifact(artifactId, fileName);
}
public void UploadDocument(string artifactId,string file)
{
projectWorkflowService.UploadActivityArtifact(StageId, artifactId, file);
var projectDocuments = projectWorkflowService.LoadPeerReviewDocuments(StageId);
projectDocuments.Items.Add(new ActivityArtifact { FileName = string.Empty });
StepTaskViewInfo.PeerReview.Documents = projectDocuments;
}
private void GetUsers()
{
foreach (ProjectPeerReview t in StepTaskViewInfo.PeerReview.Reviews.Items.ToList())
{
if (string.IsNullOrEmpty(t.Id))
{
if (!string.IsNullOrEmpty(t.Alias))
{
User current = userService.SearchAlias(t.Alias);
if (current == null)
{
MessageBox.Show("Could not find reviewer " + t.Alias);
StepTaskViewInfo.PeerReview.Reviews.Items.Remove(t);
}
else
{
t.Name = current.Name;
}
}
}
}
}
internal User[] GetSearchingUsersName(string name)
{
return userService.Search(name);
}
#endregion
public void UpdateTaskStatus(object selectedValue)
{
foreach (var task in StepTaskViewInfo.CurrentStep.ProjectTasks)
{
task.Status = selectedValue.ToString();
}
}
public void LoadTasksForCurrentStep()
{
StepTaskViewInfo.CurrentStep.ProjectTasks = projectWorkflowService.LoadProjectTasks( StepTaskViewInfo.CurrentStep.Id);
StepTaskViewInfo.UpdateTaskResources();
}
public void SaveCurrentTasksWithStep()
{
if (StepTaskViewInfo.CurrentStep != null)
{
projectWorkflowService.SaveTasksWithStep(StageId, StepTaskViewInfo.CurrentStep, StepTaskViewInfo.CurrentStep.ProjectTasks);
}
}
public bool SaveData()
{
if (StepTaskViewInfo.CurrentStep != null)
{
GetUsers();
return projectWorkflowService.SaveStepTaskViewInfo(StepTaskViewInfo, StageId);
}
return true;
}
}
}
DataGridColumn.IsReadOnly property will make all cells in the column either ReadOnly or not ReadOnly. DataGridCell.IsReadOnly property is not assignable too. What you are trying to do is to make a column readonly if some other property is false, else column should be editable. It is better to use a template column and set the control accordingly. MSDN
For example, if I want user to edit CarNumber property value is HasCar property value is true else not, then if I write :
<DataGridTextColumn Header="CarNumber" Binding="{Binding CarNumber}" IsReadOnly="{Binding HasCar"/>
//
This will still allow all column values to be editable irrespective of HasCar value.
//
If I use Template column and use DataTrigger then I get the desired effect. This wont allow user to type/edit CarNumber if HasCar property is false :
<DataGridTemplateColumn Header="CarNumber" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox x:Name="TxtCarNumber" Text="{Binding CarNumber}" >
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding HasCar}" Value="False">
<Setter Property="TextBox.Visibility" Value="Hidden"/>
<Setter Property="TextBox.Width" Value="100"/>
</DataTrigger>
<DataTrigger Binding="{Binding HasCar}" Value="True">
<Setter Property="TextBox.Visibility" Value="Visible"/>
<Setter Property="TextBox.Background" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
In your code do this, as you want your CheckBox to remain visible but not enabled :
<DataGridTemplateColumn Width ="60" HeaderStyle="{StaticResource HeaderStyle}" Header="Selected" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid IsEnabled={Binding StepTaskViewInfo.AreStepsTasksReadonly}>
<CheckBox VerticalAlignment="Center" HorizontalAlignment="Center"
IsChecked="{Binding IsSelected,UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding IsTaskEnabled,UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
I have a view model as follows:
public class SolutionViewModel : TreeViewItemViewModel {
public ObservableCollection<TreeViewItemViewModel> Children {
get { return mChildItems; }
}
public bool IsExpanded {
get { return mIsExpanded; }
set {
if (value != mIsExpanded) {
mIsExpanded = value;
OnPropertyChanged("IsExpanded");
}
if (mIsExpanded && mParentItem != null)
mParentItem.IsExpanded = true;
if (this.HasDummyChild) {
Children.Remove(EmptyItem);
LoadChildren();
}
}
}
public bool IsSelected {
get { return mIsSelected; }
set {
if (value != mIsSelected) {
mIsSelected = value;
OnPropertyChanged("IsSelected");
}
}
}
protected override void LoadChildren() {
var subFolders = default(ReadOnlyCollection<Folder>);
if (!GetSubFolders(mSolution.Folder.Name, out subFolders)) {
subFolders = new ReadOnlyCollection<Folder>(new List<Folder>());
}
foreach (var folder in subFolders) {
Children.Add(new SolutionItemViewModel(this, folder));
}
}
public string SolutionName {
get { return mSolution.Name; }
}
}
The .Xaml for the TreeView is as follows:
<TreeView Name="SolutionTree" ItemsSource="{Binding SolutionViewModel}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type localmodels:SolutionViewModel}"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<Image Width="16" Height="16" Margin="3,0" Source="..\Resources\Folder_25x25.png" />
<TextBlock Text="{Binding SolutionName}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
After a file is selected from SolutionExplorer:
public void SetBindingContext(SolutionViewModel SolutionViewModel) {
SolutionTree.DataContext = SolutionViewModel;
}
This is a lazy loading model so that when the item is expanded, the children are loaded.
The problem is that I am not even getting the Solution Name as the top level node.
Update:
I verified that the model has SolutionName assigned:
In addition, per comment from #elgonzo, I edited the .Xaml to reflect a change to ItemsSource binding:
<TreeView Name="SolutionTree" ItemsSource="{Binding}">
<TreeView.ItemContainerStyle>
Update 2
Code to assign the TreeView data context is executed when an event handler is raised after selecting a file from the OpenFileDialog:
private void OnOpenFile(string FilePath) {
mSolutionManager = SolutionManager.Load(FilePath);
mSolutionViewModel = new SolutionViewModel(mSolutionManager.Solution);
mMainWindow.SolutionExplorer.SetBindingContext(mSolutionViewModel);
mSolutionViewModel.Refresh();
}
When I step into the Refresh() method:
public void Refresh() {
OnPropertyChanged("SolutionName");
}
...I find that the PropertyChangedEventHandler has no subscribers.
Your binding to ItemsSource is wrong :
<TreeView Name="SolutionTree" ItemsSource="{Binding SolutionViewModel}">
ItemSource needs to be an IEnumerable.
In the XAML below, the command property is correctly working only with Column B. Clicking on Column A's header is not invoking command's execute method. The difference is that in Column B, DataGridColumnHeader is instantiated explicitly.
On other hand, the second part of style, the trigger that sets background base on Pressed state is working for both columns.
Why does the Command property work with Column B & not with Column A ?
<DataGrid x:Name="Test"
ItemsSource="{Binding Items}"
AutoGenerateColumns="False" >
<DataGrid.ColumnHeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Command"
Value="{Binding MyCommand}"/>
<Setter Property="CommandParameter"
Value="{Binding Path=Content, RelativeSource={RelativeSource Self}}"/>
<Style.Triggers>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.ColumnHeaderStyle>
<DataGrid.Columns>
<DataGridTextColumn FontSize="12"
Header="Column A"
Width="200"
Binding="{Binding AData}" />
<DataGridTextColumn FontSize="12"
Width="200"
Binding="{Binding BData}">
<DataGridTextColumn.Header>
<DataGridColumnHeader Content="Column B" />
</DataGridTextColumn.Header
>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
EDIT
Data context:
namespace TestColumnHeaderCommandBinding
{
public class Item
{
public int AData { get; set; }
public string BData { get; set; }
}
public class MyCommand : ICommand
{
#region ICommand Members
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
MessageBox.Show(parameter.ToString() + " clicked");
}
#endregion
}
public class DataContext
{
public DataContext()
{
MyCommand = new TestColumnHeaderCommandBinding.MyCommand();
Items = new List<Item>(5);
Items.Add(new Item { AData = 1, BData = "One" });
Items.Add(new Item { AData = 2, BData = "Two" });
Items.Add(new Item { AData = 3, BData = "Three" });
Items.Add(new Item { AData = 4, BData = "Four" });
Items.Add(new Item { AData = 5, BData = "Five" });
}
public MyCommand MyCommand { get; set; }
public List<Item> Items { get; set; }
}
}
If you don't explicitly specify the content of the header, it will just contain a simple string, which will not inherit the DataContext of the containing DataGrid.
You should see a warning in the Visual Studio output window containing something like this:
'MyCommand' property not found on 'object' ''String'
To fix that, you can make the binding target the DataContext of the DataGrid:
<Setter Property="Command"
Value="{Binding DataContext.MyCommand,
RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" />