|
You don't appear to be setting the DataContext[^] anywhere.
In the constructor, after you've created the list, try adding:
DataContext = this;
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
I am working on this[^] application.
The outter set of tabs is on the MainWindowView and the inner tabs are each on their own user control. IN the MainWindowViewMode I have a method called LoadView:
private void loadView(View View, _ArgsBase Args = null)
{
ViewInfo tabInfo = new ViewInfo { View = View, EntityId = 0};
if (TabManager.IsTabOpen(tabInfo))
{
TabManager.ActivateTab(tabInfo);
}
else
{
_DataEntryViewModelBase vm = null;
UserControl view = null;
string headerText = string.Empty;
switch (View)
{
case View.Dashboard:
headerText = "Dashboard";
vm = new DashboardViewModel();
view = new DashboardView();
break;
case View.JobCenter:
headerText = "Job Center";
vm = new JobCenterViewModel();
view = new JobCenterView();
break;
case View.CompanyCenter:
headerText = "Company Center";
vm = new CompanyCenterViewModel();
view = new CompanyCenterView();
break;
case View.MaterialsCenter:
headerText = "Materials Center";
vm = new MaterialsCenterViewModel();
view = new MaterialsCenterView();
break;
}
if (vm == null)
{
return;
}
vm.Load(Args);
view.DataContext = vm;
TabItem tabItem = new TabItem();
tabItem.Header = headerText;
tabItem.Content = view;
tabItem.Tag = tabInfo;
TabManager.AddTab(tabItem);
}
}
The obvious problem here is that the MainWindowViewModel is now coupled to all the child views. What is the right way to do this?
If it's not broken, fix it until it is
|
|
|
|
|
You'd have to know about the child views if you are creating them dynamically. What you DON'T have to know about is headerText and the vm.
Use a ViewLocator service or just specify it in the XAML. When the view is created, your VM will automatically be created and bound to the DataContext.
headerText should be specified in the XAML through data binding or hardcoded in the VM. The MainWindowViewModel certainly shouldn't know about it.
The real question is why you are getting a loadView method where you have access to the tab control itself. Big MVVM no-no.
TabControl should be in the main windows XAML and bound to an ItemsSource property. You shouldn't create tabs by hand, just add to the ItemsSource collection. That way you can do all the data binding in XAML.
|
|
|
|
|
Ok, I get the ViewLocator part. The rest I don't get.
I don't understand what you mean by this:
SledgeHammer01 wrote: The real question is why you are getting a loadView method where you have access
to the tab control itself.
The VM is trying to decide which view to open. Given the view type enum, how would the main window vm get a sub tab's view to open?
Can you point me to an example?
If it's not broken, fix it until it is
|
|
|
|
|
If you want to be all MVVMy, you would derive your child VMs from a common base class:
public ChildViewModel1 : ChildViewModelBase (which is derived from ViewModelBase or whatever)
{
}
public ChildViewModel2 : ChildViewModelBase
{
}
then in your MainWindowVM, you would have:
public ObservableCollection<ChildViewModelBase> ChildViews
{
}
in your MainWindowVM, if you wanted to open a view, you'd new up a ChildViewModel1 for example and stick it in the list. Closing a view by removing it, etc.
The MainViewView.xaml's TabControl ItemsSource would bind to the ChildViews prop. You can have a Title property in ChildViewBase that specifies the tab text.
You wouldn't use a ViewLocator in this implementation because you are going the other way. You only new up the VMs.
You would map the VM back to a view by defining DataTemplates that say for a type ChildViewModel1, I want to instantiate ChildView1 or whatever.
That doesn't happen automatically, so you'd either derive from TabControl or use an attached property where you monitor the TabControl ItemSource collection and look up the DataTemplate for the type of VM and new up the view and set its DataContext to the content of the tab (the VM) and then the tab's content to the view.
Very slick and MVVMy once you get it set up.
|
|
|
|
|
Ok, I et what you're saying, but how does binding the list of child views to the tab control item source actually render the view?
If it's not broken, fix it until it is
|
|
|
|
|
You would have a resource dictionary, which would define data templates for each view/viewmodel
<DataTemplate DataType="{x:type viewmodels:ChildViewModel1}">
<views:ChildView1/>
</DataTemplate>
You would add this resource dicyionary to your App.xaml file
When I was a coder, we worked on algorithms. Today, we memorize APIs for countless libraries — those libraries have the algorithms - Eric Allman
modified 9-Mar-13 5:00am.
|
|
|
|
|
Ok, I see. I did this once a while back - back when I knew nothing about WPF. Someone showed it to me and I coded it. Didn't understand how it worked. It's all coming together now.
Thanks!
If it's not broken, fix it until it is
|
|
|
|
|
Kevin Marois wrote: Thanks!
Glad to help!
When I was a coder, we worked on algorithms. Today, we memorize APIs for countless libraries — those libraries have the algorithms - Eric Allman
|
|
|
|
|
By the way, do you know of any resources, book, articles, etc, that discuss this? I have been Googling, but I always get back fragments.
Thanks for your help.
If it's not broken, fix it until it is
|
|
|
|
|
Not really. Its mostly basic WPF concepts put together. Data templates, data binding, monitoring changes in a collection through INotifyCollectionChanged / OnItemsSourceChanged subscribing / unsubscribing to the collection changes.
As for doing the code that implements the add / remove tabs, you could do it via an attached property or deriving from the TabControl.
Concept is, if you get a new item in the collection, its going to be a ChildViewModelBase, it's going to be assigned to the tab content. So you'd do a GetType() on it and try to find a DataTemplate that is specified for that type, then you can create the child view automatically. You'd set that as the content and set the DataContext of the view as the VM.
|
|
|
|
|
You should try to read Josh Smith's article on MSDN[^] that puts it all in one place. This article is where I learned most of my MVVM stuff from.
When I was a coder, we worked on algorithms. Today, we memorize APIs for countless libraries — those libraries have the algorithms - Eric Allman
|
|
|
|
|
I've used a separate 'controller' class for this kind of scenario.
Let the viewmodel know about this controller class and then let this 'controller' or 'loader' class decide which view needs to be loaded.
Decouples the viewmodel from all the views.
|
|
|
|
|
MVVM doesn't have a controller. Also, it already has a mechanism for mapping VMs to Views... DataTemplates.
|
|
|
|
|
SledgeHammer01 wrote: MVVM doesn't have a controller
I agree. But you can always build one.
You can call it controller or view loader or whatever you like.
The idea is to decouple the viewmodel from the logic of instantiating the appropriate view.
|
|
|
|
|
Yeah, thats what DataTemplates do .
|
|
|
|
|
Been messing around with this all day and its not working for some reason. I know as you expand a node, you have to wait for the ItemContainerGenerator to do its thing, which is what I'm doing, but its only expanding one level for some reason:
internal void ExpandAll(TreeViewItem tvi)
{
if (tvi.IsExpanded)
{
for (int nIndex = 0; nIndex < tvi.Items.Count; nIndex++)
{
TreeViewItem tviCurrent = tvi.ItemContainerGenerator.ContainerFromIndex(nIndex) as TreeViewItem;
if ((object)tviCurrent != null)
ExpandAll(tviCurrent);
}
}
else
{
if (tvi.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated)
{
EventHandler handler = null;
handler = new EventHandler(delegate
{
if (tvi.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
ExpandAll(tvi);
}
});
tvi.ItemContainerGenerator.StatusChanged += handler;
}
tvi.SetCurrentValue(TreeViewItem.IsExpandedProperty, true);
}
}
So I only get inside the EventHandler one time. It doesn't get in there for the child nodes. If I expand the child node manually, THEN the handler code is called.
Also tried the built in .Net ExpandSubTree and it has the same issue. Only expands one level down.
I don't think I'm intercepting anything anywhere.
|
|
|
|
|
I used this article [^]as the basis of my treeview item, it binds the IsExpanded to a property and that drives the tree. I extended it somewhat to meet my needs but it was an excellent concept and worked very well.
Never underestimate the power of human stupidity
RAH
|
|
|
|
|
Yeah, I have support for binding the IsExpanded property to a property in the data object in my customized TreeView control, but... I dunno... just seems like a hokey requirement to require the user of my control to maintain item state. My custom TreeView control also has built in support for displaying checkboxes next to each item. I can see binding the checkbox to a property in the data object since that kind of seems like a legit requirement. If the user is using checkbox mode, he is going to want to know the checked state of each item. He isn't really going to care about whether a node is expanded or not. Now he *might* if he wants to save the tree state, so I definitely want to keep that as an option, but I was trying to avoid relying on that to make the treeview work in general.
|
|
|
|
|
I have a tag on this discussion at telerik [^]and thought it may be relevant to your issue
Never underestimate the power of human stupidity
RAH
|
|
|
|
|
|
The WPF toolkit on codeplex has a very good auto complete text box. Just google it as I haven't figured out how to copy paste on my tablet yet, sorry.
When I was a coder, we worked on algorithms. Today, we memorize APIs for countless libraries — those libraries have the algorithms - Eric Allman
|
|
|
|
|
Can someone point me to a decent tutorial on how to override a WPF TextBox? I tried Googling, but the answers are all over the board. not sure what I'm looking for. I just want to add various functionality, and I'm looking for a starting point
If it's not broken, fix it until it is
|
|
|
|
|
This MSDN paper[^] explains the basics. You just need to add the parts that are specific to your needs.
Use the best guess
|
|
|
|
|
If you think you need to override a WPF control, you are almost always wrong. WPF allows you to attach behaviour to existing controls using Blend Behaviors.
|
|
|
|