I'm trying to get this to work but unfortunately I have no idea what I'm doing wrong! I've looked into every uestion asked so far containing TreeView and the HierarchicalDataTemplate.
Here's the problem: I have a TreeView and would like to show the folder structure with all SubFolders and Files in it. To do so I created a class with the necessary Items:
public class FolderItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public FolderItem()
{
Files = new ObservableCollection<FileItem>();
SubFolders = new ObservableCollection<FolderItem>();
}
#region:PrivateVariables
private DirectoryInfo _Info;
private ObservableCollection<FileItem> _Files;
private ObservableCollection<FolderItem> _SubFolders;
#endregion
public DirectoryInfo Info
{
get { return _Info; }
set
{
_Info = value;
OnPropertyChanged("Info");
}
}
public ObservableCollection<FileItem> Files
{
get { return _Files; }
set
{
_Files = value;
OnPropertyChanged("Files");
}
}
public ObservableCollection<FolderItem> SubFolders
{
get { return _SubFolders; }
set
{
_SubFolders = value;
OnPropertyChanged("SubFolders");
}
}
}
public class FileItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#region:PrivateVariables
private FileInfo _Info;
#endregion
public FileInfo Info
{
get { return _Info; }
set
{
_Info = value;
OnPropertyChanged("Info");
}
}
}
In the code I now loop through all Folders and Files and the ObservableCollection folders now shows the correct data! The code I use looks like this:
public void StartExtraction(string sPath)
{
if (Directory.Exists(sPath))
{
FolderItem newFolder = new FolderItem();
newFolder.Info = new DirectoryInfo(sPath);
GetFileCount(sPath, newFolder);
Application.Current.Dispatcher.Invoke((Action)delegate ()
{
ViewModel_ZVLB.folders.Add(newFolder);
});
}
}
public void GetFileCount(string sPath, FolderItem actualFolder)
{
if (Directory.Exists(sPath))
{
foreach (string fileName in Directory.GetFiles(sPath))
{
FileItem newFile = new FileItem();
newFile.Info = new FileInfo(fileName);
actualFolder.Files.Add(newFile);
}
foreach (string subFolder in Directory.GetDirectories(sPath))
{
FolderItem newSubFolder = new FolderItem();
newSubFolder.Info = new DirectoryInfo(subFolder);
actualFolder.SubFolders.Add(newSubFolder);
GetFileCount(subFolder, newSubFolder);
}
}
}
With this OC I go to the XAML and tried a lot to show the Data:
<TreeView ItemsSource="{Binding folders}">
<TreeView.Resources>
<HierarchicalDataTemplate ItemsSource="{Binding folders}" DataType="{x:Type local:FolderItem}">
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Info.Name}" />
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.Resources>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Info.FullName}" FontSize="16" FontWeight="Bold" />
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Even with another HierarchicalDataTemplate it won't work.
Is there anything I did wrong? Or is it just not working with ObservableCollections?
Intereisting is also that after the Update of Visual Studio 2017 a new Error appeared: As "folders" for Tpye "ViewModel_ZVLB" a instancemenber is expected! (translated from German)
Has this something to do with my problem?
Thanks for your help!
In my opinion, if you want to work with Treeviews, it is easier to use only one Observablecollection for the children.
First I create a generic class for each element of your Tree. The Folder and File classes inherits from it.
public class TreeItem: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class FolderItem:TreeItem
{
public FolderItem()
{
Elems = new ObservableCollection<TreeItem>();
}
#region:PrivateVariables
private DirectoryInfo _Info;
private ObservableCollection<TreeItem> _Elems;
#endregion
public DirectoryInfo Info
{
get { return _Info; }
set
{
_Info = value;
OnPropertyChanged("Info");
}
}
public ObservableCollection<TreeItem> Elems
{
get { return _Elems; }
set
{
_Elems = value;
OnPropertyChanged("Elems");
}
}
}
public class FileItem : TreeItem
{
#region:PrivateVariables
private FileInfo _Info;
#endregion
public FileInfo Info
{
get { return _Info; }
set
{
_Info = value;
OnPropertyChanged("Info");
}
}
}
Here is the XAML code:
<TreeView ItemsSource="{Binding folders}">
<TreeView.Resources>
<HierarchicalDataTemplate ItemsSource="{Binding Elems}" DataType="{x:Type local:FolderItem}">
<TextBlock Text="{Binding Info.Name}" FontWeight="Bold"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:FileItem}">
<TextBlock Text="{Binding Info.Name}" />
</DataTemplate>
</TreeView.Resources>
</TreeView>
Of course you need to update your filling function:
public void GetFileCount(string sPath, FolderItem actualFolder)
{
if (Directory.Exists(sPath))
{
foreach (string fileName in Directory.GetFiles(sPath))
{
FileItem newFile = new FileItem();
newFile.Info = new FileInfo(fileName);
actualFolder.Elems.Add(newFile);
}
foreach (string subFolder in Directory.GetDirectories(sPath))
{
FolderItem newSubFolder = new FolderItem();
newSubFolder.Info = new DirectoryInfo(subFolder);
actualFolder.Elems.Add(newSubFolder);
GetFileCount(subFolder, newSubFolder);
}
}
}
Related
I'm pretty new to programming with WPF and C# and I have a question regarding the possibility to automatically check all the CheckBoxes in a Listbox. I'm developing a plugin for Autodesk Revit and, after having listed all the names of the rooms in a list box, I want to check them all using the button "Check All"
I've read the thread at this page but still, I'm not able to make it work. May someone help me with my code?
Here is what I've done:
XAML:
<ListBox x:Name='roomlist'
SelectionMode='Multiple'>
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked='{Binding IsChecked}'
Content="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.InputBindings>
<KeyBinding Command="ApplicationCommands.SelectAll"
Modifiers="Ctrl"
Key="A" />
</ListBox.InputBindings>
<ListBox.CommandBindings>
<CommandBinding Command="ApplicationCommands.SelectAll" />
</ListBox.CommandBindings>
</ListBox>
C#
public partial class RoomsDistance_Form : Window
{
UIDocument _uidoc;
Document _doc;
public RoomsDistance_Form(Document doc, UIDocument uidoc)
{
InitializeComponent();
FilteredElementCollector collector = new FilteredElementCollector(doc)
.WhereElementIsNotElementType()
.OfCategory(BuiltInCategory.OST_Rooms);
List<String> myRooms = new List<String>();
foreach (var c in collector)
{
myRooms.Add(c.Name);
}
myRooms.Sort();
roomlist.ItemsSource = myRooms;
}
private void checkAllBtn_Click(object sender, RoutedEventArgs e)
{
foreach (CheckBox item in roomlist.Items.OfType<CheckBox>())
{
item.IsChecked = true;
}
}
public class Authority : INotifyPropertyChanged
{
private bool isChecked;
public bool IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
NotifyPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Thank you very much for your help!
In the thread you are linking to, they are setting the "IsChecked" on the data object (Authority), not the CheckBox control itself.
foreach (var a in authorityList)
{
a.IsChecked = true;
}
You have a binding to IsChecked that will update the Checkbox control when NotifyPropertyChanged() is called.
After having lost my mind in the effort i solved my problem by avoiding the Listbox.. I simply added single CheckBoxes in the StackPanel.
XAML:
<ScrollViewer Margin='10,45,10,100'
BorderThickness='1'>
<StackPanel x:Name='stack'
Grid.Column='0'></StackPanel>
</ScrollViewer>
C#:
foreach (var x in myRooms)
{
CheckBox chk = new CheckBox();
chk.Content = x;
stack.Children.Add(chk);
}
Not what i was looking for but now it works and that's the point.
Thank you for your help!
I usually use CheckBoxList in the following way:
In xaml:
<ListBox ItemsSource="{Binding ListBoxItems, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"> //+some dimensional properties
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Name}" IsChecked="{Binding IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In xaml.cs:
public partial class MyWindow : Window
{
public ViewModel ViewModel {get; set; }
public MyWindow(ViewModel viewModel)
{
//keep all the mess in ViewModel, this way your xaml.cs will not end up with 1k lines
ViewModel = viewModel;
DataContext = ViewModel;
InitializeComponent();
}
void BtnClick_SelectAll(object sender, RoutedEventArgs e)
{
ViewModel.CheckAll();
}
}
ViewModel preparation:
public class ViewModel
{
public List<ListBoxItem> ListBoxItems { get; set; }
//InitializeViewModel()...
//UpdateViewModel()...
//other things....
public void CheckAll()
{
foreach (var item in ListBoxItems)
{
item.IsSelected = true;
}
}
public class ListBoxItem : INotifyPropertyChanged
{
public string Name { get; set; }
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
if (_isSelected != value)
{
_isSelected = value;
OnPropertyChanged(nameof(IsSelected));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
One of the proprties in my ViewModel is a collection of objects. I cant figure it out how to bind my inner DataTemplate to the properties of these objects inside the collection: The problematic binding is in TestDataTemplate1, it just cannot find N and STR.. Here is my XAML
<Page.Resources>
<DataTemplate x:Key="TestDataTemplate1">
<Grid Background="Cornsilk" HorizontalAlignment="Stretch" Height="Auto">
<TextBlock x:Name="LeftTB" Text="{Binding N}" Foreground="Red" HorizontalAlignment="Left" VerticalAlignment="Stretch"/>
<TextBlock x:Name="RightTB" Text="{Binding STR}" Foreground="Green" HorizontalAlignment="Right" VerticalAlignment="Stretch"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="TestDataTemplate">
<Grid Background="Yellow" HorizontalAlignment="Stretch" Height="Auto">
<TextBlock x:Name="LeftTB" Text="{Binding Name}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
<ListBox x:Name="MyListBox" x:FieldModifier="Public"
ItemsSource="{Binding SampleCollection}"
ItemTemplate="{StaticResource TestDataTemplate1}"
Background="Blue"
HorizontalAlignment="Center"
VerticalAlignment="Stretch" Width="200">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
<TextBlock x:Name="RightTB" Text="{Binding Age}" HorizontalAlignment="Right" VerticalAlignment="Center"/>
</Grid>
</DataTemplate>
</Page.Resources>
This is my Type that contains these properties:
class AlaBalaVM : INotifyPropertyChanged
{
AlaBala _ab = null;
public AlaBalaVM(AlaBala ab)
{
_ab = ab;
}
public string N
{
get
{
return "num: " + _ab._n.ToString();
}
set { RaisePropertyChanged("N"); }
}
public string STR
{
get
{
return _ab._str.ToString() + " string";
}
set { RaisePropertyChanged("STR"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And this is my ViewModel in which I fill the DataSource collection:
public class PersonVMWrapper : INotifyPropertyChanged
{
PersonModel _pm = null;
public PersonVMWrapper(PersonModel pm)
{
_pm = pm;
}
public string Name
{
get
{
return "mr." + _pm.Name;
}
set { RaisePropertyChanged("Name"); }
}
public string Age
{
get
{
return _pm.Age.ToString() + " years";
}
set { RaisePropertyChanged("Age"); }
}
public ObservableCollection<AlaBala> SampleCollection
{
get
{
return _pm.SampleCollection;
}
set { RaisePropertyChanged("SampleCollection"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class PersonVM : INotifyPropertyChanged
{
DispatcherTimer _refreshTimer = new DispatcherTimer();
private ObservableCollection<PersonVMWrapper> personDataSource;
public PersonModel PM { get; set; }
public PersonVM()
{
AddItemToDataSource(null);
}
private void AddItemToDataSource(object o)
{
DataSource.Add(new PersonVMWrapper(new PersonModel() { Name = "asdasd", Age = 23, SampleCollection = new ObservableCollection<AlaBala> { new AlaBala(3, "e1"), new AlaBala(4, "e2") } }));
}
public ObservableCollection<PersonVMWrapper> DataSource
{
get
{
if (this.personDataSource == null)
{
this.personDataSource = new ObservableCollection<PersonVMWrapper>();
}
return this.personDataSource;
}
set
{
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
You haven't posted the definition of the "Alabala" class -- does it contain the "N" and "STR" properties? As #KooKiz pointed out, it looks like you meant to make "SampleCollection" an ObservableCollection<AlaBalaVM>, ie:
SampleCollection = new ObservableCollection<AlaBalaVM>
{
new AlaBalaVM(new AlaBala(3, "e1")),
new AlaBalaVM(new AlaBala(4, "e2")),
}
I have a ListBox that is populated through a ViewModel and a DataTemplate in which I also have a binding. The problem is the binding in the DataTemplate is not working. Here is my code:
ViewModel:
private void NotifyPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
private string strHeaderText;
public string HeaderText
{
get
{
if (null != this.Trips)
{
if (null != this._trips.name)
{
return this._trips.name;
}
}
return "Trip";
}
set
{
this.strHeaderText = value;
}
}
public TripTypeViewModel()
{
this.TripTypeViewModelDataSource.Clear();
foreach (TripType tt in TripsResponseTypeViewModelDataSource.FirstOrDefault().Trip)
{
this.TripTypeViewModelDataSource.Add(tt);
}
}
...
private ObservableCollection<TripType> tripTypeViewModelDataSource;
public ObservableCollection<TripType> TripTypeViewModelDataSource
{
get
{
if (null == this.tripTypeViewModelDataSource)
{
this.tripTypeViewModelDataSource = new ObservableCollection<TripType>();
}
return this.tripTypeViewModelDataSource;
}
}
Here is my View
<ListBox x:Name="ItemsLB" ItemsSource="{Binding tripTypeViewModel.TripTypeViewModelDataSource}">
<ListBox.ItemTemplate>
<DataTemplate>
<Button Content="{Binding tripTypeViewModel.HeaderText}" Width="Auto" Height="Auto"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I have a TreeView with Binding, but in the TreeView only 1st level items are shown. I need a treeview =) I broke my head what is wrong.
Here is my code:
MainWindow.xaml
<TreeView Margin="2.996,10,214,10" ItemsSource="{Binding Path=Urls}" Grid.Column="1">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate>
<Grid>
<Rectangle Fill="{Binding Path=Color}" HorizontalAlignment="Left" Stroke="Black" VerticalAlignment="Top" Height="20" Width="20"/>
<TextBlock Text="{Binding Path=AbsoluteUrl}" Margin="25,0,0,0" />
</Grid>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=AbsoluteUrl}" />
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
MainViewModel.cs (part)
public ObservableCollection<Url> Urls { get; set; }
public MainWindowViewModel()
{
Urls = new ObservableCollection<Url>();
}
Url.cs
class Url : INotifyPropertyChanged
{
public Url() { }
public Url(string absoluteUrl, bool isBroken, string color)
{
AbsoluteUrl = absoluteUrl;
IsBroken = isBroken;
Color = color;
}
enum Status { Working, Broken };
private ObservableCollection<Url> childUrlsValue = new ObservableCollection<Url>();
public ObservableCollection<Url> ChildUrls
{
get
{
return childUrlsValue;
}
set
{
childUrlsValue = value;
}
}
private string _absoluteUrl;
public string AbsoluteUrl
{
get { return _absoluteUrl; }
set
{
if (_absoluteUrl != value)
{
_absoluteUrl = value;
OnPropertyChanged("AbsoluteUrl");
}
}
}
private bool _isBroken;
public bool IsBroken
{
get { return _isBroken; }
set
{
if (_isBroken != value)
{
_isBroken = value;
OnPropertyChanged("IsBroken");
}
}
}
private string _color;
public string Color
{
get { return _color; }
set
{
if (_color != value)
{
_color = value;
OnPropertyChanged("Color");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And just about this i'm adding items to Urls:
Url DataGridTopic = new Url(startUrl.ToString(), true, "red");
DataGridTopic.ChildUrls.Add(
new Url(startUrl.ToString(), true, "red"));
DataGridTopic.ChildUrls.Add(
new Url(startUrl.ToString(), true, "red"));
DataGridTopic.ChildUrls.Add(
new Url(startUrl.ToString(), true, "red"));
Urls.Add(DataGridTopic);
You will have to tell the HierarchicalDataTemplate where to get the child items of a node from by using its ItemsSource property.
In your case:
<HierarchicalDataTemplate
DataType="{x:Type my:Url}"
ItemsSource="{Binding Path=ChildUrls}"
>
...
</HierarchicalDataTemplate>
Note also the usage od the DataType attribute, which often will become a necessity if the levels of the tree are made of different object types (a tree of directories and files would be such an example). However, i am not sure whether this would apply to your scenario or not.
I have the following xaml:
<phone:PhoneApplicationPage.Resources>
<DataTemplate x:Key="OrganisationTemplate">
<Grid Margin="40,0,0,0">
<StackPanel>
<TextBlock Text="{Binding name}"></TextBlock>
</StackPanel>
</Grid>
</DataTemplate>
</phone:PhoneApplicationPage.Resources>
<phone:PanoramaItem Header="Organisations">
<ListBox Name="Organisation" ItemTemplate="{StaticResource OrganisationTemplate}" DataContext="{Binding Organisations, Mode=OneWay}" Margin="0,0,0,96"/>
</phone:PanoramaItem>
and I would like to know if the binding "Organisations" needs to be a List or ICollection or something... Or can it be an IEnumerable. It's just it currently isn't working.
class DashboardViewModel
{
private OrganisationRepository organisationRepository { get; set; }
public IEnumerable<Organisation> Organisations { get; set; }
public DashboardViewModel()
{
LoadOrganisationSection();
}
private async void LoadOrganisationSection()
{
organisationRepository = new OrganisationRepository();
Organisations = await organisationRepository.GetAll();
OnPropertyChanged("Organisations");
//LoadBar loadBar = new LoadBar("Logging in");
//loadBar.ShowLoadBar(this);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler tempEvent = PropertyChanged;
if (tempEvent != null)
{
// property changed
tempEvent(this, new PropertyChangedEventArgs(propertyName));
}
}
}
EDIT:://
public IEnumerable<Organisation> Organisations
{
get
{
return new Organisation[] { new Organisation { name = "hi" } };
}
set
{
OnPropertyChanged();
}
}
If I do this, I get something back out, so it's the trigger that's not working. any ideas how i'm to do this? when my await on organisationRepository.GetAll() finishes.. I need the onchange to happen and update. doh
Try setting the listbox binding to the ItemsSource instead of setting its datacontext. (assuming your viewmodel is already set as the datacontext of the view)