I have one problem using TreeView in WPF using the MVVM design pattern.I found a solution that helped me a lot for understanding the principals, but when I applied it in my project, I can't get the desired results. The article is this one.
My base class is:
public class ClientTreeViewProducts
{
public List<ClientTreeViewProducts> Items { get; set; }
public string Name { get; set; }
}
In the ViewModel I have a public property that I bind to the ItemsSource property of the TreeView:
private List<ClientTreeViewProducts> _treeViewSource;
public List<ClientTreeViewProducts> TreeViewSource
{
get { return _treeViewSource; }
set
{
if(_treeViewSource!=value)
{
_treeViewSource = value;
RaisePropertyChanged("TreeViewSource");
}
}
}
I have a function that fills the List, and it seems to be OK.
And finally here is my xaml that manages the bindings and the HierarchicalDataTemplate :
<TreeView ItemsSource="{Binding TreeViewSource}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type helpers:ClientTreeViewProducts}"
ItemsSource="{Binding Items}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
The problem is when I fill some data in the TreeViewSource property, the tree view remains empty (visually). Can someone find what the problem is, because I'm head-banging about this problem for 3 days.
Thank you in advance!
Related
It is necessary to display the element, presented in the form as in the picture, in the form of a tree. The object itself is created at compile time.
An attempt to make in this form
<TreeView
Grid.Row="1"
Grid.Column="0"
ItemsSource="{Binding LanguageInformation.Items}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:LocalizationBrowserViewModel}" ItemsSource="{Binding LanguageInformation.Items}">
<TextBlock Text="{Binding}"/>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
Class for presentation
public class Element
{
public string Name { get; set; }
public ObservableCollection<object> Items { get; set; }
public Element()
{
this.Items = new ObservableCollection<object>();
}
}
The Element class now allows only one level of hierarchy. To support N levels, the Element class needs to be changed as below
public class Element
{
public string Name { get; set; }
public ObservableCollection<Element> Items { get; set; }
public Element()
{
this.Items = new ObservableCollection<Element>();
}
}
TreeView needs to be replaced with a ListView because the screenshot provided in the question contains multiple columns. TreeView does not allow multiple columns but a ListView.
ListView in WPF supports HierarchicalDataTemplate. You can refer HierarchicalDataTemplate in WPF for the implementation.
I am currently having a class myCommand
class myCommand : INotifyCollectionChanged , INotifyPropertyChanged
{
public string Name { get; set; }
public ObservableCollection<myFile> subCommand { get; set; }
}
I have two TreeView items in my window.
tvCommandList which contains all available commands and tvFinalList to hold all selected commands.
I used contextmenu to copy the items from tvCommandList to tvFinalList;
In the mainWindow, I have two ObservableCollection items which are bound to the TreeViewItems.
ObservableCollection<myCommand> cmdlist = null;
ObservableCollection<myCommand> finallist = null;
These are bound to the TreeView in the XAML file.
<Grid>
<Grid.Resources>
<ResourceDictionary>
<Style x:Key="styleTemplate" TargetType="TreeViewItem">
<Setter Property="IsSelected" Value="{Binding IsInitiallySelected, Mode=TwoWay}" />
</Style>
<HierarchicalDataTemplate DataType="{x:Type data:myCommand}"
ItemsSource="{Binding subCommand, Mode=TwoWay}">
<TextBlock Text="{Binding Name, Mode=TwoWay}" />
</HierarchicalDataTemplate>
</ResourceDictionary>
</Grid.Resources>
</Grid>
<TreeView x:Name="tvSendList" ItemsSource="{Binding}" DataContext="{Binding cmdlist}">
<TreeView x:Name="tvRecvList" ItemsSource="{Binding}" DataContext="{Binding finallist}">
I copy TreeViewItem from cmdlist to finallist and edit them for customsing data.
The issue I am facing here is that if I modify an item (Update the Name value) in the finallist, the cmdlist item also gets updated, which
I am not sure how to go about to resolve this.
I tried moving the ResourceDictionary to each TreeView specifically, but still facing the same issue
You should also clone, i.e. create a copy of, each individual myFile object when you create a copy of the collection, e.g.:
finalist = new ObservableCollection<myFile>(cmdlist.Select(x => new myFile()
{
Property1 = x.Property1,
Property2 = x.Property2
//copy all property values...
}));
I am trying to get the values/contents of the "selected" check-boxes so that i can use it to get data from sqlite database in the back-end . But I am unable to get the value of the checkbox from the listview.
This is the listview -
<ListView x:Name="listview" Background="Azure" SelectionMode="Multiple"
ItemsSource="{Binding Strings}" RenderTransformOrigin="0.5,0.5" Width="343">
<ListView.ItemTemplate>
<DataTemplate x:Name="datatemplate">
<CheckBox x:Name="CheckBoxZone" IsChecked="{Binding RelativeSource={RelativeSource AncestorType=ListViewItem},Path=IsSelected}"
Content="{Binding que_text}" Margin="0,5,0,0"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Style="{StaticResource ResourceKey=button_style }" x:Name="addToSurvey" Content="Add" Click="AddToSurvey_Click" />
this is the function-
private void AddToSurvey_Click(object sender, RoutedEventArgs e)
{
//foreach (var item in listview.SelectedItems)
for (int i=0;i< listview.SelectedItems.Count;i++)
{
string que = listview.Items[i].ToString(); //did not work
}
}
this is the Questions class -
public class Questions
{
public int que_id { get; set; }
public string que_text { get; set; }
}
the checkbox hold the que_text value which I need to retrieve for getting the que_id from the database.
In WPF, we use what we call MVVM. Instead of going and poking at the ListViewItem controls like in winforms, we'll put properties for the information we need on our viewmodel classes, and we'll use bindings to tell the UI how to update the viewmodel classes and vice versa.
So we'll add an IsSelected property to Question. We'll also rename that class from Questions to Question, and the collection of questions will now be named Questions instead of Strings.
public class Question
{
public int ID { get; set; }
public string Text { get; set; }
public bool IsSelected { get; set; }
}
Here's your ListView. We bind CheckBox.IsSelected to ListViewItem.IsSelected, so the user can check them just by clicking anywhere on the item. Then we bind Question.IsSelected to ListViewItem.IsSelected in an ItemContainerStyle.
<ListView
x:Name="listview"
Background="Azure"
SelectionMode="Multiple"
ItemsSource="{Binding Questions}"
>
<ListView.ItemTemplate>
<DataTemplate>
<CheckBox
IsChecked="{Binding RelativeSource={RelativeSource AncestorType=ListViewItem}, Path=IsSelected}"
Content="{Binding Text}" Margin="0,5,0,0"
/>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</ListView.ItemContainerStyle>
</ListView>
And here's how we do stuff with the selected questions in that event handler. I'm guessing that your Strings collection was a member of the Window or whatever view you have; let me know if that's not the case and we'll figure out how to get to it. Remember, we're calling that collection Questions now.
private void AddToSurvey_Click(object sender, RoutedEventArgs e)
{
string allQuestionsText = "";
foreach (var question in Questions.Where(q => q.IsSelected))
{
// I don't know what you really want to do in here, but it sounds like you do.
allQuestionsText += question.Text + "\n";
}
}
I know I really should start reading a book about XAML and WPF because I think all my Problems here belong to a lack of understanding about Data Binding (I used WinForms for years):
My Application consists of a TreeView and a DataGrid.
In the TreeView I have added ViewModels for each ParentNode, ChildNode an GrandChildNode.
I've used the sample from Josh Smith found here.
To be short, he/I used
<HierarchicalDataTemplate
DataType="{x:Type local:ViewModel.TreeViewChildNodeViewModel}"
ItemsSource="{Binding Children}">
</HierarchicalDataTemplate.Resources>
to bind the ChildNode to a ChildNodeViewModel and to the corresponding Model.
I than added - in the TreeViewChildNodeViewModel constructor:
ContextMenuItems = new List<MenuItem>();
ContextMenuItems.Add(new MenuItem() {
Header = "Click",
Command = _cmdDoSmth
ToolTip = new ToolTip() { Content = "blabla" }
}
);
which is exposed to the View with this property:
private readonly List<MenuItem> ContextMenuItems;
public List<MenuItem> ContextMenu {
get { return ContextMenuItems; }
}
Note that, I have multiple constructors. I add different ContextMenuItems to the ContextMenu List depending on what Model i want the ViewModel to work with. The "root" ChildNode consist of a:
<TextBlock
Text="{Binding ChildNodeDisplayItem}">
<TextBlock.ContextMenu>
<ContextMenu
ItemsSource="{Binding ContextMenu}"></ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
That works like it should. Now my problems start with trying to do some similar with the datagrid.
What I need to achieve is:
I'd like to show rows in the datagrid. Each Row has its own Viewmodel with an exposed List of ContextMenuItem's (as well as the model of course). I'd like to be able to define the count, header and command of each contextmenuitem in dependence of the viewmodel that is selected.
What I did so far:
In my MainWindow.xaml:
<Controls:MetroWindow.Resources>
<ContextMenu x:Key="DataRowContextMenu" ItemsSource="{Binding Path=ActionReactionDataGridViewModel/ContextMenu, RelativeSource={RelativeSource AncestorType=DataGrid, Mode=FindAncestor}}"/>
</Controls:MetroWindow.Resources>
<DataGrid
AutoGenerateColumns="True"
AutoGeneratingColumn="OnAutoGeneratingColumn"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
BorderThickness="1,1,1,1"
Margin="0,0,0,0"
ItemsSource="{Binding Path=ActionReactionDataGridViewModel/DataGridSource}"
SelectedItem="{Binding Path=ActionReactionDataGridViewModel/SelectedDataGridItem}"
BorderBrush="#FF020202">
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="ContextMenu" Value="{StaticResource RowMenu}" /> </Style>
<DataGrid.RowStyle>
</DataGrid>
In my MainWindowViewModel:
public MainWindowViewModel() // Constructor
{
actionReactionDataGrid = new ObservableCollection<ActionReactionDataGridViewModel>();
actionReactionDataGrid.Add(new ActionReactionDataGridViewModel());
}
private ObservableCollection<ActionReactionDataGridViewModel> actionReactionDataGrid;
public ObservableCollection<ActionReactionDataGridViewModel> ActionReactionDataGridViewModel
{
get { return actionReactionDataGrid; }
}
My ActionReactionDataGridViewModel is here:
public class ActionReactionDataGridViewModel : ViewModelBase
{
private readonly List<MenuItem> ContextMenuItems;
public ActionReactionDataGridViewModel()
{
ContextMenuItems = new List<MenuItem>();
ContextMenuItems.Add(new MenuItem()
{
Header = "blubb"
});
dataGridSource = new ObservableCollection<ActionReactionDataGridModel>();
dataGridSource.Add(new ActionReactionDataGridModel("Status","Eventname","Eventtyp","ReaktionName","ReaktionTyp"));
}
public List<MenuItem> ContextMenu {
get { return ContextMenuItems; }
}
private ActionReactionDataGridModel selectedDataGridItem;
public ActionReactionDataGridModel SelectedDataGridItem {
get { return selectedDataGridItem; }
set {selectedDataGridItem = value; RaisePropertyChanged("SelectedDataGridItem"); }
}
private ObservableCollection<ActionReactionDataGridModel> dataGridSource;
public ObservableCollection<ActionReactionDataGridModel> DataGridSource {
get { return dataGridSource; }
set { dataGridSource = value; RaisePropertyChanged("DataGridSource"); }
}
}
I think posting the content of the model is not neccessary because it just contains the column headers and some sample strings. I think what iam missing is the knowledge of telling the DataGrid Control in the View in MainWindow.xaml to bind the itemssource to "DataGridSource" instead of "ActionReactionDataGridViewModel".
I found other posts on SO about adding Context Menus to a datagridrow. what i was missing is the ability to bind the count, text and command to each viewmodel.
Any Help would be greatly appreciated.
Thank you.
// EDIT 1
Ok. finding out how to pass the property of a viewmodel from inside a collection of viewmodels was easy.
i added
ItemsSource="{Binding Path=ActionReactionDataGridViewModel/DataGridSource}
explanation is here
Now I "just" need to figure out how to add the contextmenu items to each viewmodel...
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="HeaderName">
</MenuItem>
</ContextMenu>
</DataGrid.ContextMenu>
inside menu item you can write your control.
I have a really weird issue with Combobox in WPF. I have used it before in a simpler implementation and thought I knew how it works, but apparently not. The scenario is as follows... I have three classes which are in a parent/child relationship, I have a list of a type AnswerViewModel which I populate in a Combobox. I want the IsSelected property on the class to be the value that is get/set in the Combobox. Below is some of the code, hopefully enough to see what I am referring to.
These are the models.
public class Quiz
{
public string QuizName {get;set;}
public List<Question> Questions {get;set;}
}
public class Question
{
public int QuestionId {get;set;}
public string QuestionText {get;set;}
public List<Answer> {get;set;}
}
public class Answer
{
public int AnswerId {get;set;}
public string AnswerText {get;set;}
public bool IsSelected {get;set;}
}
These are the ViewModels
QuizViewModel.cs
// This will create QuestionViewModel for each question.
ObservableCollection<QuestionViewModel> Questions {get;set;}
QuestionViewModel.cs
// This will create AnswerViewModel for each answer
ObservableCollection<AnswerViewModel> Answers {get;set;}
AnswerViewModel.cs
Answer answer;
// ctor
public AnswerViewModel(Answer answer...)
{
this.answer = answer;
...
}
public string AnswerText
{
get { return answer.AnswerText; }
set
{
answer.AnswerText = value;
NotifyPropertyChanged("AnswerText");
}
}
public bool IsSelected
{
get { return _answer.IsSelected; }
set
{
answer.IsSelected = value;
NotifyPropertyChanged("IsSelected"); // <--- Breakpoint
}
}
These are the XAMLs
Quiz.xaml
<ItemsControl ItemsSource="{Binding Questions}" />
Question.xaml
<ComboBox x:Name="answerCombo"
ItemsSource="{Binding Answers}"
DisplayMemberPath="AnswerText"
SelectedValue="{Binding Path=IsSelected}"
SelectedValuePath="IsSelected"/>
The Combobox is populated correctly (It is bound to the Collection of AnswerViewModel), but when I make a selection what I expect is to see at the breakpoint is two events. One for the false on the item no longer selected, and true for the new item selected. But instead it never gets hit.
So I scour the web looking for help and found some interesting information and tried a number of things but, so far nothing seems to be complete. For example:
<ComboBox x:Name="answerCombo"
ItemsSource="{Binding Answers}"
DisplayMemberPath="AnswerText"
SelectedValue="{Binding Path=IsSelected}"
SelectedValuePath="IsSelected">
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}"/>
</Style>
</ComboBox.ItemContainerStyle>
</Combobox>
But this only selects the correct item in the Combobox when drop-down. Which I understand is correct for this markup.
So, in short, I would like to have the IsSelected Property on the AnswerViewModel updated when I select an item in the Combobox. Any help would be greatly appreciated.
If more information is required just let me know.
Thank you,
Obscured
** Update **
Ok, I think I actually resolved it, but still If anyone has suggestions, or thoughts on this, I would still like to hear them.
Here is what I did, I only changed the Combobox xaml as follows:
<ComboBox x:Name="answerCombo"
ItemsSource="{Binding Answers}"
DisplayMemberPath="AnswerText"
SelectedValue="{Binding Path=ItemsSource/, RelativeSource={RelativeSource Self}}">
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}"/>
</Style>
</ComboBox.ItemContainerStyle>
</Combobox>
Basically this sets the selectedvalue to the current item, and since the itemcontainerstyle handles the case when drop-down it binds the IsSelected property of the combobox to the IsSelected property of my ViewModel. And this appears to do the trick.
Just remove the SelectedValue and SelectedValuePath properties, it's not how they work. SelectedValuePath indicates which property of the selected item will be the SelectedValue of the ComboBox; if you say that the SelectedValuePath is "IsSelected", the SelectedValue will be SelectedItem.IsSelected, which is clearly not what you want... And your binding for SelectedValue refers to a property that doesn't exist (QuestionViewModel.IsSelected)
This should work for you:
<ComboBox x:Name="answerCombo"
ItemsSource="{Binding Answers}"
DisplayMemberPath="AnswerText">
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}"/>
</Style>
</ComboBox.ItemContainerStyle>
</Combobox>
have you try:
<ComboBox x:Name="answerCombo"
ItemsSource="{Binding Answers}"
DisplayMemberPath="AnswerText"
SelectedValue="{Binding IsSelected,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="IsSelected"/>