In my program I have a TreeView that is implemented through a ViewModel by using an ObservableCollection. Each collection has a property called Rank. This is supposed to serve as the collection item's index.
In this question, I was able to get my TreeView nodes to switch places using ObservableCollection.Move();
However, after switching the places of the nodes, I need to correct/change the value of the nodes' rank, so that I can continue to manipulate them.
This should help explain what I am doing:
View -- Code-Behind:
//Button Click Event -- This makes the Selected Node switch places with the node above it
private void shiftUp_Click(object sender, RoutedEventArgs e)
{
//if a node is selected
if (UCViewModel.TreeViewViewModel.SelectedItem != null)
{
//If the selected Node is not in the 0 position (can not move up anymore)
if (UCViewModel.TreeViewViewModel.Collection<TreeViewModel>.IndexOf(UCViewModel.TreeViewViewModel.SelectedItem) != 0)
{
int oldIndex = UCViewModel.TreeViewViewModel.SelectedItem.Rank;
int newIndex = oldIndex--;
UCViewModel.TreeViewViewModel.Collection<TreeViewModel>.Move(oldIndex, newIndex);
//**Pseudo code trying to explain what I want to do
//**get item at specific index and change the Rank value
//Collection item at index (newIndex).Rank -= 1;
//Collection item at index (oldIndex).Rank += 1;
}
}
}
UserControl -- XAML:
<TreeView ItemsSource="{Binding TreeViewViewModel.Collection<TreeModel>}" ... />
How can I correct the Rank values after the move?
EDIT
As stated above, I have a Rank property in the Data Model of my TreeView. #Noctis's answer recommends using that property to sort my TreeView after the Rank values are changed. This is demonstrated by my favorite question on this topic, here.
I've added the SortObservableCollection class to my program, so now all that's left is to manipulate rank values, and sort. Would the correct place to do this be from the code-behind? Basically where the above^ section is from? If that is the case, I get a little confused about the exact calls...
Code-Behind:
private void shiftUp_Click(object sender, RoutedEventArgs e)
{
//if a node is selected
if (UCViewModel.TreeViewViewModel.SelectedItem != null)
{
//Moves the selectedNode down one (Up visually, hence shiftUp)
UCViewModel.TreeViewViewModel.SelectedItem.Rank--;
//How would I get the node below the selected one and change the Rank?
//This would be the call to sort. Which needs to be called for the collection
//For some reason, sort does not come up for the collection...
//UCViewModel.TreeViewViewModel.Collection.**Sort(...);
}
}
Instead of actually moving your items around, you could change the actual values on your objects ranks (assuming they are public properties), and let the sorting be done automatically for you via the binding ...
Edit:
This is embarrassing, and as your comment says, my link is to winforms.
having said that, have a look at this wonderful answers by rachel, wpf-it, and the self answered jeremy .
They either implemented a comparer (the direction i was going to with the msdn link), or used the default headers.
That should give you a great start and example on how to do it with your rank.
Edit:
On the Xaml:
<StackPanel x:Name="LayoutRoot">
<TreeView Name="TestTreeView" ItemsSource="{Binding MyTree}">
<TreeView.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Black" BorderThickness="1" Margin="2" Padding="2">
<TextBlock Text="{Binding Path=Name}"/>
</Border>
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>
<Button Command="{Binding SortMe_Command}">Sort</Button>
On the view model I have a simple class like this (with rank, for you):
public class MyTreeClass
{
public string Name { get; set; }
public int Rank { get; set; }
}
I've added rachel's class as is:
public class SortableObservableCollection<T> : ObservableCollection<T> { ...}
Property for the binding:
public SortableObservableCollection<MyTreeClass> MyTree
{
get { return _myTree; }
set { _myTree = value; }
}
private SortableObservableCollection<MyTreeClass> _myTree;
Command for the action:
public ICommand SortMe_Command { get; set; }
In the constructor:
MyTree = new SortableObservableCollection<MyTreeClass>() {new MyTreeClass(){Name = "One",Rank = 1},
new MyTreeClass(){Name = "Two",Rank = 2},
new MyTreeClass(){Name = "Three",Rank = 3}};
SortMe_Command = new RelayCommand<object>(Execute_SortMe);
SortMe_Command = new RelayCommand<object>(Execute_SortMe);
And last but not least, the execute method:
private void Execute_SortMe(object obj)
{
MyTree[0].Rank = 5;
MyTree[1].Rank = 4;
MyTree.Sort(node => node.Rank);
}
Now when I click the button, it will change 2 item's rank, and resort the tree according to rank.
In your application, just figure out which one's you're swapping, and you're set ...
Related
I want to detect how many files are in a directory, then use that number to add the same number of items to a ComboBox. But when I create the items, how do I give each item an individual name? The 'Add' only gives the item its content, but I want to give it an x:Name. This is what I have so far:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
if (!File.Exists(#"C:\_Kooper Young FBLA\CIC=true.txt"))
{
int FileAmount = Directory.GetFiles(#"C:\_Kooper Young FBLA").Length;
for (int i = 1; i < FileAmount + 1; i++)
{
ComboBox.Items.Add();
}
}
File.Create(#"C:\_Kooper Young FBLA\CIC=true.txt");
}
As you are in WPF, I would better advise you to use Binding for your ComboBox.
Here is a simple way to do it, creating a class ComboItem :
public class ComboItem
{
public string FileName { get; set; }
}
So in your code you just need to do the following :
public ObservableCollection<ComboItem> ComboFiles { get; }
= new ObservableCollection<ComboItem>();
foreach (var file in Directory.GetFiles(directoryName))
{
ComboFiles.Add(new ComboItem { FileName = file });
}
so you can populate your list of ComboBox.
Then in your xaml you can bind to your ObservableCollection like that :
<ComboBox Grid.Column="1" ItemsSource="{Binding Path=ComboFiles}"
SelectedItem="{Binding Path=SelectedFile}" DisplayMemberPath="FileName"/>
Where SelectedFile is a ComboItem Object.
Of course the answer is not "key in hand", but making a bit of research about Binding I am sure you will reach it quickly, I only can advise you that link
I hope I'm understanding your question correctly. You can try to create a Model for your files, whatever they represent. I'll say PersonModel and then try ObservableCollection<PersonModel> People = new ObservableCollection<PersonModel>();
On your PersonModel you'll need properties for whatever information you need to store about a Person like Name.
Then write a foreach loop that looks at each item in your directory and creates a PersonModel for each one found, and sets the Name property somehow.
Finally at the end, but still inside the loop, do People.Add(item);
In Xaml bind the ItemsSource property of your combobox to the People collection. Set DisplayMemberPath of your combobox to Name.
I'm new to the MVVM pattern and I have an assignment to implement a TreeView which acts like a work space viewer (sort of like eclipse's/vs's solution explorer).
The tree view can contain multiple types of items (like a file, a folder of other items etc).
I have created the Models (the Folder's model have Children (which can be any kind of item), Name etc).
Example model:
public class SystemFolder: IWorkspaceItem
{
string mTitle;
public ObservableCollection<IWorkspaceItem> Children { get; set; }
public string Path
{
get { return mTitle; }
set
{
mTitle = value;
OnPropertyChanged("Title");
}
}
//Constructor
public SystemFolder(string name, ItemType type, string path)
: base(name, type)
{
Path = path;
Children = new ObservableCollection<IWorkspaceItem>();
//add children here...
}
//Some more code here..
}
I've created a ViewModel for each model (Which all store the model's instance).
The ViewModels have the necessary properties that the view needs (like Title, Children etc).
Example ViewModel:
public class SystemFolderViewModel : TreeViewModelItem
{
SystemFolder mFolder;
ObservableCollection<TreeViewModelItem> mChildren;
public ObservableCollection<TreeViewModelItem> Children
{
get { return mChildren; }
}
//====================
// Constructor
//====================
public SystemFolderViewModel(SystemFolder folder, TreeViewModelItem parentWorkspaceItem)
: base(parentWorkspaceItem)
{
mFolder = folder;
mFolder.Attach(OnItemPropertyChanged);
}
public string Name
{
get { return mFolder.Name; }
set { Name = value; }
}
public string IconPath
{
get { return mFolder.ItemType.IconPath; }
set { IconPath = value; }
}
//Some more code here..
}
I've also implemented the View which defined the HierarchicalDataTemplate of each ViewModel.
It all works fine and I actually made a mockup.
Example of HierarchicalDataTemplate in View:
<HierarchicalDataTemplate
DataType="{x:Type local:SystemFolderViewModel}"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<Image Width="16" Height="16" Margin="3,0" Source="{Binding Path=IconPath}"/>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</HierarchicalDataTemplate>
The whole initialization:
Workspace data_model = new Workspace("Main Workspace", PredefinedTypes.GetWorkspaceType(), Database.GetItems());
TreeViewModel vm = new TreeViewModel(data_model);
WorkspaceView viewer = new WorkspaceView(vm);
//add to grid etc..
Now my problem is that my program is dynamic. Meaning, items can be added to the TreeView in run-time.
Now let me explain how I understand MVVM should work and please correct me.
The Data model provides the view with items.
So my program should only add items to the data model.
The main ViewModel of the tree holds a DataModel instance which hold the workspace's main children.
Each item that is added to the model should automatically update the ViewModel which in turn should update the view.
Since I only update the DataModel, when I add a child to one of its items, the ViewModel should find the corresponding item in the ViewModel and add a new child to it.
Why do I have to have two seperate collections? The DataModel's Children and the ViewModel Children.
I need every data model to inherit from INotifyPropertyChanged so it will update its ViewModel.
Also as I said, there are data models which have their own children. If that collection changes I need the collection in the item's ViewModel to change.
This all seems kind of dumb to me. Why should I get through all this trouble? Why can't I just add items to the ViewModel which will update the view?
What am I missing?
How do I need to go about with this?
If you need more info, please ask me and I will provide/explain.
Thank you!
Dolev.
I have the below UI, which allows you to select a team in the left, then edit properties of the selected team on the right. Here's an example scenario demonstrating the issue:
Select Dragon team
Rename to Smaug, press save.
"Dragon" in selection panel on left doesn't update to "Smaug". However, if I select another team, the reselect "Dragon", the textbox on the right side still (correctly) shows "Smaug". I'm pretty sure this means that the databound collection is correctly being updated.
Close the Settings window, then reopen it.
Left panel now (correctly) shows "Smaug".
The list of teams is being stored as an observable collection:
public class TeamList : ObservableCollection<Team>
{
public TeamList() : base() { }
}
Team list on the left is being populated/bound:
SettingsWindow.xaml
<ListView ItemsSource="{Binding}" Grid.Column="0" Grid.Row="1" DisplayMemberPath="name"
SelectionChanged="ListTeamSelected" SelectionMode="Single">
<!--<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Foreground" Value="{Binding color}" />
</Style>
</ListView.ItemContainerStyle>-->
</ListView>
SettingsWindow.xaml.cs
public Team selectedTeam { get; set; }
public SettingsWindow()
{
teams = TeamManager.Instance().teamList;
this.DataContext = this.teams;
if (!Application.Current.Resources.Contains("selectedTeam"))
Application.Current.Resources.Add("selectedTeam", selectedTeam);
InitializeComponent();
}
Data on the right is being populated and saved:
SettingsWindow.xaml.cs
private void ClickSaveData(object sender, RoutedEventArgs e)
{
selectedTeam.name = TeamName.Text;
selectedTeam.color = PrimaryColorPicker.SelectedColor;
selectedTeam.secondaryColor = SecondaryColorPicker.SelectedColor;
saved = true;
}
private void ListTeamSelected(object sender, RoutedEventArgs e)
{
selectedTeam = (Team)(sender as ListView).SelectedItems[0];
TeamInfo.Visibility = Visibility.Visible;
TeamName.Text = selectedTeam.name;
PrimaryColorPicker.SelectedColor = selectedTeam.color;
SecondaryColorPicker.SelectedColor = selectedTeam.secondaryColor;
}
Twofold question:
Am I doing anything wrong with my databinding that's causing this issue? (I'm new at WPF)
If not, is there a way for me to force the UI to update the list on the left? (this seems vaguely hacky to me)
Thank you in advance for any assistance!
Your properties need to implement the INotifyPropertyChanged interface for databinding to work properly.
For example (from http://wpftutorial.net/INotifyPropertyChanged.html)
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
PropertyChanged("Name");
}
}
private void PropertyChanged(string prop)
{
if( PropertyChanged != null )
{
PropertyChanged(this, new PropertyChangedEventArgs(prop);
}
}
I would highly recommend the MVVM Light Toolkit for any MVVM work though (https://mvvmlight.codeplex.com/). If you use the MVVM Light Toolkit then you can inherit from ViewModelBase and then implement your properties like this
private string _orgId;
public string OrgId
{
get { return _orgId; }
set { _orgId = value; RaisePropertyChanged("OrgId"); }
}
I didn't know to have the my Team class implement INotifyPropertyChanged. This link was very helpful and straightforward. A couple things to note for others who have never worked with data-binding before:
You need getters and setters for all the properties you want to notify on in order to throw the event.
You need to use private variables for holding the data itself, or the setter will trigger itself, throwing you into a stack overflow.
The parameter for the event is the public name of the property that was changed, not the private name nor the value.
Thanks to #ReggaeGuitar for the answer!
This part is ready and operational:
"How to bind a column of ComboBox"
<DataGridTemplateColumn Header="Bot Plate Thickness">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding SteelThickness, RelativeSource={RelativeSource AncestorType=Window}}" SelectedItem="{Binding BottomPlateThickness, UpdateSourceTrigger=PropertyChanged}" SelectionChanged="ComboBox_SelectionChanged" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
And this is the relevant part of my model:
public class GridModel : PropertyChangedBase
{
private string _BottomPlateThickness;
public string BottomPlateThickness
{
get
{
return _BottomPlateThickness;
}
set
{
if (_BottomPlateThickness != value)
{
_BottomPlateThickness = value;
RaisePropertyChanged("BottomPlateThickness");
}
}
}
}
This is another part:
public List<string> SteelThickness { get; set; }
SteelThickness = new List<string> { "0.3750", "0.4375", "0.5000", "0.6250", "0.7500", "0.8650", "1.0000" };
As you can see, the ComboBox contents are based on a static list. I read that in order to be able to be turned on/off, I have to base that column on an ObservableList<object>.
TIA.
The answer is simple... you don't access a ComboBox in a specific row of a DataGridTemplateColumn. Why do so many people want to use WPF like WinForms?... it's not WinForms. I say this line so much that I'm starting to feel like a preacher... WPF is a data-centric language... that means that we manipulate data objects, not UI objects. Why not make your life easier and hold the metaphoric hammer by the handle instead of the head?
Thinking of this problem in terms of data, we have a collection of data objects. One object is represented by one row in the DataGrid, so one property of these objects will relate to the selected item from the ComboBoxes in question. Now if you were to make your life really easy, you'd add a new collection property into that class that you could bind to the ComboBox.ItemsSource property.
Now, to change the possible options in each ComboBox, all you need to do is to change the items in that collection property in your data bound object. You could define your 'unfiltered' collection in your data type as a static variable that each instance could share:
private static ObservableCollection<string> steelThickness = new
ObservableCollection<string> { "0.3750", "0.4375", "0.5000", "0.6250", "0.7500",
"0.8650", "1.0000" };
public ObservableCollection<string> ComboBoxItemsSource
{
get { return new ObservableCollection<string>(
steelThickness.Where(item => item.MeetsCertainCriteria(item))); }
}
private bool MeetsCertainCriteria(string item)
{
// return true or false for each item to adjust the members in the collection
// based on whatever your condition is
if ( ... ) return true;
return false;
}
The only points to note with this set up is that you'll have to manually call NotifyPropertyChange("ComboBoxItemsSource") when the collection is updated and that you'll need to use a OneWay Binding because it has no setter. Now, wasn't that easier?
Rewritten:
I could use some input, suggestions, and samples from some other minds on building a databindable collection.
The collection needs to provide a databindable, editable tree of items, but with a small twist: the items need to be one of two types, with each type providing slightly different characteristics. The two types of items are Folder and TreeItem. A Folder contains it's own list of items (again, either of Folder or TreeItem type) and a TreeItem does not contain a list.
My current approach is fairly close, but feels crufty. Essentially I have an abstract base class, TreeItemBase, which (in a round-about way) inherits from BindableList. Then I have two concrete derived types, Folder and TreeItem which both inherit from the abstract base class. The obvious flaw is that the TreeItem, which can't contain childitems, still inherits from BindingList; so it's up to some ugly hackery to pretend that it's not a collection.
Is BindingList<> a poor choice to base this collection on? Some of the DataBinding interfaces sound like they offer a higher degree of control over the databinding, but I haven't found one that's quite right. My ideal though is to provide a custom implementation that lets me control how databinding walks the collection and can inspect the concrete type of each element to determine if it contains a collection, or if it's a terminus in the tree.
Here's a quick cut of the XML to help visualize what I'm trying to represent; it's easy for me to cleanly code the structure and rules in XSD--but I'm just having a hard time translating it to .NET and supporting databinding.
<Items>
<TreeItem xsi:type="Folder" name="Root">
<TreeItem xsi:type="Folder" name="Sub1">
<TreeItem xsi:type="TreeItem" name="Humm"/>
</TreeItem>
<TreeItem xsi:type="TreeItem" name="Bleh"/>
<TreeItem xsi:type="Folder" name="Sub2">
<TreeItem xsi:type="TreeItem" name="Boo!"/>
</TreeItem>
</TreeItem>
</Items>
Update: I've been working more on my approach and have come close to what I'd like to do using an interface rather than a base-class for the items, but have hit a snag. The issue I've run into is covered on a seperate question.
Ideally I'd like to use the abstract base-class approach so that the XML that's generated considers Folder and TreeItem to be complexTypes (without manual control of the serialization), but it's a negligible requirement.
Maybe my depth of knowledge isn't big enough for this question, but couldn't you do this:
Have an interface, and 2 classes that implement the interface.
interface ITreeItem
{
IEnumerable<ITreeItem> GetChildren();
}
class MyFolder : ITreeItem
{
public IEnumerable<ITreeItem> GetChildren()
{
// TODO: Return the list of children
}
}
class MyITreeItem : ITreeItem
{
public IEnumerable<ITreeItem> GetChildren()
{
// TODO: Return the list of children
}
}
Then if your goal is to databind the collection to some list, you should be able to do so with the IEnumerable collections. In each call to databind the collections, you should be able to check to see which type the item is:
foreach (var node in root.GetChildren())
{
if (node is MyFolder)
{
var folder = (MyFolder)node;
// Bind fields from the folder object
}
else if(node is MyTreeItem)
{
var folder = (MyTreeItem)node;
// Bind fields from the tree item object
}
}
I did something similar (I think) to this when I had a list nested inside another list. To display the data, I setup Nested ListView controls.
Sorry if this isn't what you're looking for, but hope it helps!
SkippyFire's solution seems more elegant than mine, but I figured that I will show you how I solved the problem. The following solution shows what I did to build a collection that can be bound to a tree view, and you can determine which items have been selected. It does not implement any Bindable Lists or anything though. However, it is not clear from your post whether this is what you want.
This is an example of my XML file:
<controls name="Parent" attribute="element">
<control name="Files" attribute="element">
<control name="Cashflow" attribute="element">
<control name="Upload" attribute="leaf"/>
<control name="Download" attribute="leaf"/>
</control>
</control>
<control name="Quotes" attribute="element">
<control name="Client Quotes" attribute="leaf"/>
</control>
</controls>
Then I have a class that represents each item. It contains a Name, a List of child nodes (1 level down), a reference to its parent, and a string that logs the attribute of the element.
public class Items
{
public string Name { get; set; }
public List<Items> SubCategories { get; set; }
public string IsLeaf { get; set; }
public string Parent { get; set; }
}
From there, I populate a list of Items as follows:
List<Items> categories = new List<Items>();
XDocument categoriesXML = XDocument.Load("TreeviewControls.xml");
categories = this.GetCategories(categoriesXML.Element("controls"));
This calls the GetCategories() method
private List<Items> GetCategories(XElement element)
{
return (from category in element.Elements("control")
select new Items()
{
Parent = element.Attribute("name").Value,
Name = category.Attribute("name").Value,
SubCategories = this.GetCategories(category),
IsLeaf = category.Attribute("attribute").Value
}).ToList();
}
After the categories variable has been populated, I just assign the list as the treeview's ItemSource.
controltree.ItemsSource = categories;
And from there, if the choice changes in the tree, I check if the choice is a leaf node, and if so, I raise an event.
private void Selection_Changed(object sender, RoutedEventArgs e)
{
Items x = controltree.SelectedItem as Items;
if (x.IsLeaf.Equals("leaf"))
_parent.RaiseChange(x.Parent+","+x.Name);
}
This solution works for any depth in the tree as well.
I've used: treeviewadv from source forge. It have a very nice MVC way of dealing with tree view type modeling. It is a windows forms control that binds a model to a treeview style control with support for columns. They also provide some nice sample code.
ObservableCollection is perhaps the best bet for you, coupled with two DataTemplates.
public class TreeItem : INotifyPropertyChanged
{
public string Name { get; set; }
// ...
}
public class Folder : TreeItem
{
public ObservableCollection<TreeItem> Items { get; private set; }
public Folder()
{
this.Items = new ObservableCollection<TreeItem>();
}
// ...
}
And your DataTemplate's (including the Secret Sauce HierarchicalDataTemplate):
<HierarchicalDataTemplate DataType="{x:Type local:Folder}"
ItemsSource="{Binding Path=Items}">
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:TreeItem}">
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
Putting it all together (code behind):
public class FolderList : ObservableCollection<TreeItem>
{
public FolderList()
{
this.Add(new TreeItem { Name = "Hello" });
this.Add(new TreeItem { Name = "World" });
var folder = new Folder { Name = "Hello World" };
folder.Items.Add(new TreeItem { Name = "Testing" });
folder.Items.Add(new TreeItem { Name = "1" });
folder.Items.Add(new TreeItem { Name = "2" });
folder.Items.Add(new TreeItem { Name = "3" });
this.Add(folder);
}
}
XAML:
<Grid>
<Grid.Resources>
<local:FolderList x:Key="MyItems" />
<HierarchicalDataTemplate DataType="{x:Type local:Folder}"
ItemsSource="{Binding Path=Items}">
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:TreeItem}">
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
</Grid.Resources>
<TreeView>
<TreeViewItem ItemsSource="{Binding Source={StaticResource MyItems}}"
Header="Root" />
</TreeView>
</Grid>