I have a TreeView in my WPF application.Now as per my requirement i want to export into Excel format but i am not getting how to start with it.
Here is my TreeView Creation C# Code..
private void TreeView_Loaded(object sender, RoutedEventArgs e)
{
// ... Create a TreeViewItem.
item.Header = "Computer";
item.ItemsSource = new string[] { "Monitor", "CPU", "Mouse" };
// ... Create a second TreeViewItem.
TreeViewItem item2 = new TreeViewItem();
item2.Header = "Outfit";
item2.ItemsSource = new string[] { "Pants", "Shirt", "Hat", "Socks" };
// ... Get TreeView reference and add both items.
var tree = sender as TreeView;
tree.Items.Add(item);
tree.Items.Add(item2);
}
private void TreeView_SelectedItemChanged(object sender,
RoutedPropertyChangedEventArgs<object> e)
{
var tree = sender as TreeView;
// ... Determine type of SelectedItem.
if (tree.SelectedItem is TreeViewItem)
{
// ... Handle a TreeViewItem.
var item = tree.SelectedItem as TreeViewItem;
this.Title = "Selected header: " + item.Header.ToString();
}
else if (tree.SelectedItem is string)
{
// ... Handle a string.
this.Title = "Selected: " + tree.SelectedItem.ToString();
}
}
Please help me to get it exported into Excel.
Thanks in advance..
As mentioned in your other question, in WPF, we data bind properties to UI controls, so in your case you should have a collection property data bound to the TreeView.ItemsSource property. If you did, then all you'd need to do is to iterate through that collection and populate your Excel data from it directly.
So your first error is trying to populate your TreeView from the TreeView_Loaded event handler. I already told you what you needed to do, so I'm a bit surprised that you completely ignored my advise and continued along the incorrect path. Define a collection property to data bind to the TreeView.ItemsSource property... you also need to declare a custom data type class with the required properties, eg. one collection property to data bind to the child node's ItemsSource property:
public ObservableCollection<YourClass> Items
{
get { return items; }
set { items = value; NotifyPropertyChanged("Items"); }
}
Then we data bind this to the TreeView.ItemsSource property:
<TreeView ItemsSource="{Binding Items}" />
Finally, we need to declare a HierarchicalDataTemplate to define what each item should look like:
<TreeView ItemsSource="{Binding Items}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding ItemsPropertyInYourClass}">
<TextBlock Text="{Binding NameOfPropertyInYourClass}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
So that's how you display items in a TreeView properly... please see the TreeView and HierarchicalDataTemplate, Step-by-Step page on MSDN for further help with this.
Now all you need to do is to iterate through your collection to populate your Excel data. Rather than going through all of that code now, I'd rather direct you to read an online tutorial, so please take a look at the Export data to Excel using C# on matijabozicevic.com.
You still need to read the Data Binding Overviewā€ˇ page on MSDN for further help with data binding.
Related
I am using ComboBox in wpf as below and want to update ComboBox behind the seen if i update collection :-
<xmlns:dataProvider="clr-namespace:DataProvider"
<UserControl.Resources>
<dataProvider:BackOfficeDataProvider x:Key="DataProvider"/>
</UserControl.Resources>
<ComboBox x:Name="groupGroupNameCombo" HorizontalAlignment="Left" Margin="368,123,0,0" VerticalAlignment="Top" Width="226" Height="31" SelectionChanged="groupGroupNameCombo_SelectionChanged" DisplayMemberPath="GroupName" SelectedItem="{Binding ParentID, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" ItemsSource="{Binding GroupParentList, Mode=TwoWay, NotifyOnTargetUpdated=True, NotifyOnSourceUpdated=True, Source={StaticResource DataProvider}}" IsSynchronizedWithCurrentItem="True">
</ComboBox>
Class BackOfficeDataProvider {
public static ObservableCollection<Categories> groupParentList = null;
public virtual ObservableCollection<Categories> GroupParentList
{
get { return groupParentList ; }
set
{
groupParentList = value;
// Call OnPropertyChanged whenever the property is updated
OnPropertyChanged("GroupParentList");
}
}
public void loadComboListData();
{
GroupParentList = (ObservableCollection<Categories>) //fetching data from database using NHibernate directly getting list ;
}
}
my front end class which has refresh button :-
private void RefreshButton_Click(object sender, RoutedEventArgs e)
{
new BackOfficeDataProvider().loadComboListData();
}
when application load that time i can see the item in combobox but when i click on Refresh button that time it load updated data from database but not updating combobox untill i use below code
groupGroupNameCombo.ItemsSource = null;
groupGroupNameCombo.ItemSource = GroupParentList ;
Its a manually thing i have to do always to refresh combobox, how can i make it automatic like if i update collection then it should update combobox at the same time and i don't need to use above workaround.
I think this may have something to do with breaking the coupling between the combobox and the ObservableCollection when doing this:
GroupParentList = //fetching data from database;
Try this instead:
var dbCategories = // Get data from DB
GroupParentList.Clear();
foreach (var item in dbData)
GroupParentList.Add(item);
The point is to update the items in the collection, not the collection itself.
Also, try defining your collection like this, it should'nt have to be instantiated more than once (i.e no setter):
public static ObservableCollection<Categories> groupParentList = null;
public virtual ObservableCollection<Categories> GroupParentList
{
get
{
if (groupParentList == null)
groupParentList = new ObservableCollection<Categories>();
return groupParentList;
}
}
Hogler is right, your approach of assigning a new ObservableCollection object to the binding property will break how binding works. For ObservableCollection to work, you will need to modify the items in the collection itself, ObservableCollection is responsible of publishing list changes to the binding target. When you assign a new collection to the binding target, the list will not get refresh unless you published PropertyChanged event again to register this new binding source.
In your later comment you did state that you only instantiate ObservableCollection once only, which is not obvious from your posted code. It appears to me that the reason why it doesn't work is because you assign a new collection to the "GroupParentList" each time you run "loadComboListData".
Try this ..
once You are done getting data from your database in groupParentList , Add below Line, it will work as below :-
GroupParentList = new ObservableCollection<Categories>(groupParentList )
I have a WPF ListBox of Grids that I create as follows:
I define the listbox in XAML with a simple declaration as follows:
<ListBox Name="MyListbox" >
</ListBox>
In code, I dynamically create an arbitrary number of Grid items (System.Windows.Controls.Grid), and I add them to the ListBox. Something like:
foreach (MyDataType myItem in MyDataList)
{
Grid newGrid = new Grid();
// Code that sets the properties and values of the grid, based on myItem
MyListbox.Items.Add(newGrid);
}
This works great, and everything looks the way that I want it to.
Now, I've decided that in my ListBox, I also want to store a reference to the actual myItem object, so that I can reference it later.
My idea was to create a new class like:
public class ListGridItemNode
{
public MyDataType theItem;
public Grid theGrid;
public ListGridItemNode(MyDataType inItem, Grid inGrid)
{
theItem = inItem;
theGrid = inGrid;
}
}
And then change my code to:
foreach (MyDataType myItem in MyDataList)
{
Grid newGrid = new Grid();
// Code that sets the properties and values of the grid, based on myItem
MyListbox.Items.Add(new ListGridItemNode(myItem,newGrid));
}
Of course, now my listbox instead of displaying the grids, just displays the text "MyApp.ListGridItemNode".
My question is: How do I tell the ListBox to go a level deeper and display the actual Grids inside of each ListGridItemNode object?
I suspect that this has something to do with bindings, but I can't find any examples that work the way that I'm doing it. Most of what I'm finding only shows binding to a string within an object, not an entire control.
Couldn't you just use the Tag property of the Grid object?
newGrid.Tag = myItem;
Then later:
Grid grid; // obtain Grid object somehow
MyItem myItem = (MyItem) grid.Tag;
In my program's main window I have a TreeView and a ContentPresenter. The display of the ContentPresenter is determined by what node is selected in the TreeView.
The name of one of my nodes is allowed to be changed by the user via contentMenu. All the user has to do is right click the node and select the new name out of the choices. The ContentPresenter is supposed to have a null display until the user chooses a name for the node.
The problem occurs when a new name is selected from the contentMenu. The ContentPresenter's display changes, like it should, but only after the user selects a different node (changing the display), and then re-selects the original node.
How do I make it so that the display on the ContentPresenter changes right when the TreeView node's name is changed?
TreeViewViewModel:
public class TreeViewViewModel : PropertyChangedBase
{
public TreeViewViewModel()
{
Node = new Node() { NodeName = "Blank", NodeDataModel = new NodeModel(),
Commands = { new Command(nodeType_name1), new Command(nodeType_name2) } };
}
//These functions call to the NodeName property in the TreeView's Data Model
private void nodeType_name1()
{
Node.NodeName = "Name1";
}
private void nodeType_name2()
{
Node.NodeName = "Name2";
}
}
XAML for MainWindow:
<!-- Tree view items & Functions -->
<TreeView Name="Tree_One" ItemsSource="{Binding DataTree.Data}" ... >
<TreeView.Resources>
<SolidColorBrush Color="LightSkyBlue" x:Key="{x:Static SystemColors.HighlightBrushKey}" />
</TreeView.Resources>
</TreeView>
<!--- Left Widget -->
<ContentPresenter Content="{Binding LeftWidget}" />
MainWindowViewModel:
public class MainWindowViewModel : PropertyChangedBase
{
private TreeViewViewModel _dataTree;
public MainWindowViewModel()
{
_dataTree = new TreeViewViewModel();
}
public TreeViewViewModel DataTree { ... }
//This function is in charge of changing the display of the ContentPresenter
// I think that my problem can probably be solved by doing something here
public void ChangeViews()
{
if (_dataTree.SelectedItem is Node)
{
var _node = _dataTree.SelectedItem as Node;
var nodeViewModel = new NodeViewModel(_node.NodeDataModel);
if (_node.NodeName== "Unknown")
LeftWidget = null; //This is the Content Presenter **
if (_node.NodeName == "Name1")
{
LeftWidget = nodeViewModel;
}
if (_node.NodeName == "Name2") {...}
}
}
}
Duh, thats a alot of code and its pretty difficult to understand what you up to since you seem to have controls in your ViewModel.
Or at least it looks to me that you have them in ViewModel. That is not very MVVM-alike my friend. :)
"The problem occurs when a new name is selected from the contentMenu. The ContentPresenter's display changes, like it should, but only after the user selects a different node (changing the display), and then re-selects the original node."
The property changed is not being fired because the new selected value is equal to the old one.
Pretty obvious, right?... no property was actually changed
But why do you want the ContentPresenter to update itself with the value that it already has?
You said when you select a node the ContentPresenter displays it properly and when you re-select the same the ContentPresenter is not doing anything.
Its not doing anything because it think it doesnt need to. Which is true.
So the question is why would you make ContentPresenter force to refresh on each value no matter if old value is the same as new one?
Though if you want to hack/trick a little bit, you can always set ContentPresenter's Content to null before you assign another value. :)
However, post us more code and we will be able to provide you a better solution to your issue.
I was able to fix this issue by calling ChangeViews(); in my MainWindowViewModel from my TreeViewViewModel. I did this by using a delegate property in the TVVM, and adding it to my MWVM. By doing this, the display is updated whenever ChangeViews(); is called.
This is the answer that I used.
I have a DataGrid that gets its data updated every few seconds via a Thread. The DataGrid needs to offer Column Header sorting, grouping and filtering.
I currently have a DataGrid bound to a ICollectionView and the source of the ICollectionView is an ObservableCollection. Which seems to be the good way to do it from what I read on other threads.
The sort-ing "works" but it gets lost when the ICollectionView.Source gets updated following an update of the ObservableCollection. I have tried saving the SortDescriptions before the update and re-add it to the ICollectionView after the update is done. But it's the same result.
May someone point me to what I'm missing?
Edit Here's some code...
View (XAML)
<DataGrid ItemsSource="{Binding CollectionView, Source={StaticResource ViewModel}}>
ViewModel
public ICollectionView CollectionView
{
get
{
collectionViewSource.Source = dataColl;
if (SortDescriptions != null)
{
foreach (SortDescription sd in SortDescriptions)
{
collectionViewSource.View.SortDescriptions.Add(sd);
}
}
collectionViewSource.View.Refresh();
return collectionViewSource.View;
}
}
public ObservableCollection<SomeObject> DataColl
{
get { return dataColl; }
private set
{
this.dataColl= value;
OnPropertyChanged("CollectionView");
}
}
Following is the method that updates the data every few seconds...
private void UpdateData()
{
while (true)
{
System.Threading.Thread.Sleep(mDataRefreshRate);
// SortDescriptions is a Property of the ViewModel class.
SortDescriptions = collectionViewSource.View.SortDescriptions;
ObservableCollection<SomeObject> wDataColl
= new ObservableCollection<SomeObject>();
//... Irrelevant code that puts the data in wDataColl ...
DataColl= wDataColl;
}
}
[YourObservableCollection].ViewHandler.View.Filter
+= new FilterEventHandler(myFilterHandler);
private void myFilterHandler(object sender, FilterEventArgs e)
{
}
Can be used to directly add your filter handler and you can do the same with SortDescriptions to Add/Remove
[YourObservableCollection].ViewHandler.View.SortDescriptions.Add(mySortDescription);
If you are doing allot of sorting and filtering best to create own class encapsulating a CollectionViewSource and implement adding and removing SortDescriptions and Filtering etc
When you say:
The sort "works" but it gets lost when the ICollectionView.Source gets
updated following an update of the ObservableCollection
What do you mean by update? you mean you are changing the Source? Rather than adding/removing items from the collection?
EDIT based on your XAML example you added:
<DataGrid ItemsSource="{Binding CollectionView, Source={StaticResource ViewModel}}>
You are binding itemsource to the CollectionViewSource where you should bind the datacontext to it:
Example:
<Page.Resources>
<CollectionViewSource x:Key="myViewSource"
Source="{Binding CollectionView, Source={StaticResource ViewModel}}"
/>
</Page.Resources>
In page:
<Grid DataContext="{StaticResource myViewSource}">
<DataGrid x:Name="myGrid" ItemsSource="{Binding}"...
Or something along those lines
EDIT again didnt see code scroll down :p
ObservableCollection<SomeObject> wDataColl= new ObservableCollection<SomeObject>();
You create new instance every time of collection lol this is main problem
Also:
public ICollectionView CollectionView
{
get
{
collectionViewSource.Source = dataColl;
if (SortDescriptions != null)
{
foreach (SortDescription sd in SortDescriptions)
{
collectionViewSource.View.SortDescriptions.Add(sd);
}
}
collectionViewSource.View.Refresh();
return collectionViewSource.View;
}
}
Here where you return collection you are setting the Source and adding the SortDescriptions and also refreshing the view every time, you only need set these values once
You would only call refresh on the View if you add/remove SortDescriptions
I think you should get to grips with basics of CollectionViewSource
The problem is that you swap out the entire ObservableCollection every time you add new data.
ObservableCollection<SomeObject> wDataColl= new ObservableCollection<SomeObject>();
... Unrelevant code that puts the data in wDataColl ...
DataColl= wDataColl;
Make sure to use Add to the existing collection instead (perhaps after using Clear() first if that is necessary)... If you still have problems after that please comment and i will try to help.
Also, try to avoid using the Refresh() as it rebuilds the entire view and is unnecessarily expensive. If you do sorting, adding, removing etc. the correct way use of Refresh() isn't needed.
I have a ListBox and a class with strings. Each time that a user clicks add button in the application, I create a new instance of the class and add it to the list which is binded to the ListBox. The first time I click the add button, the list box shows the first item, but the next time it doesn't show two items.
XAML - this is the ListBox:
<ListBox Name="ListBox_BinsRegion" Height="181" Margin="233,16,6,94" Width="253" Background="Transparent" BorderThickness="1" BorderBrush="Black" ScrollViewer.VerticalScrollBarVisibility="Auto" ItemsSource="{Binding}"/>
The code behind:
List<Class_ListViewItem> List_ListBoxItems = new List<Class_ListViewItem>();
private void Button_Add_Click(object sender, RoutedEventArgs e)
{
Class_ListViewItem item = new Class_ListViewItem();
item.WH = this.comboBox_WareHouseBinsRegionDefinition.SelectedItem.ToString();
item.XXFrom = textBox_XXFrom.Text;
item.XXTo = textBox_XXTo.Text;
item.YYFrom = textBox_YYFrom.Text;
item.YYTo = textBox_YYTO.Text;
item.Z = textBox_ZFrom.Text;
List_ListBoxItems.Add(item);
ListBox_BinsRegion.DataContext = List_ListBoxItems;
}
Where is my mistake?
WPF does not know when your collection is changing. The problem is here:
List<Class_ListViewItem> List_ListBoxItems = new List<Class_ListViewItem>();
you need to change the list to
ObservableCollection<Class_ListViewItem> List_ListBoxItems = new ObservableCollection<Class_ListViewItem>();
ObservableCollection (System.Collections.ObjectModel) throws an event when the collection is changed, so that WPF can update the listbox.
Also, you can remove the following line, or move it to the constructor of your control.
ListBox_BinsRegion.DataContext = List_ListBoxItems;
You should not change the DataContext of the control, instead set the binding to theList_ListBoxItems and make it a public property, and use an ObservableCollection or BindableCollection instead of list
When you assign the DataContext the second time, it doesn't technically change. This is because you are assigning it to the same collection. You should do something like this instead:
ObservableCollection<Class_ListViewItem> List_ListBoxItems = new ObservableCollection<Class_ListViewItem>();
public YourControl() {
InitializeComponent();
ListBox_BinsRegion.DataContext = List_ListBoxItems;
}
private void Button_Add_Click(object sender, RoutedEventArgs e)
{
Class_ListViewItem item = new Class_ListViewItem();
item.WH = this.comboBox_WareHouseBinsRegionDefinition.SelectedItem.ToString();
item.XXFrom = textBox_XXFrom.Text;
item.XXTo = textBox_XXTo.Text;
item.YYFrom = textBox_YYFrom.Text;
item.YYTo = textBox_YYTO.Text;
item.Z = textBox_ZFrom.Text;
List_ListBoxItems.Add(item);
}
Use an ObservableCollection<> rather than a List<>. This will update the binding automatically, with no need for the following line (which can be kind of slow)
ListBox_BinsRegion.DataContext = List_ListBoxItems;
You could either do what everyone else already suggested (using an ObservableCollection instead of the List) - or you could query the dependency property which is bound and find the corresponding Binding and refresh it manually.
I'd go for the ObservableCollection :)