PRISM + Tabs = Pain - wpf

I am having difficulty trying to get a very simple scenario working with PRISM 2.0 for WPF. I want the main workarea of my application to be a TabControl. Each time I add a view, I want it to appear as a TabItem on the TabControl.
Sounds easy right?
My region, which is in my Shell.XAML looks like this:
<Controls:TabControl
Name="MainRegion"
cal:RegionManager.RegionName="{x:Static Infrastructure:RegionNames.TabRegion}"
ItemContainerStyle="{StaticResource ShellTabItemStyle}" />
The style: ShellTabItemStyle looks like this:
<Style x:Key="ShellTabItemStyle" TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding Content.DataContext.HeaderInfo, RelativeSource={RelativeSource Self}}" />
</Style>
This should set the Header of the TabItem to the HeaderInfo property on the DataContext of the view. (I got this idea from this article) The DataContext of my view is a very simple Presenter which has a HeaderInfo property on it:
public string HeaderInfo = "The Header Text";
My view is a simple WPF usercontrol and looks like this:
<StackPanel>
<TextBox Text="Hello World" Name="MyTextBox"></TextBox>
<Image Source="..SomeImage.PNG" Name="MyImage"></Image>
</StackPanel>
So far, so good. If I add the view to the region I get a tab control and I get a tab with the text set to "The Header Text". My problem is that there is absolutely no content appearing on the tab. My view contains a simple Image and a TextBox, but neither of them show up in the TabItem. If I break out Snoop and look around, there isn't an image in sight.
What am I missing here - is there an easier way?

I was unable to get any of the suggested answers to work. Extensive googling didn't help either. I gave the problem some thought over the weekend and more I thought about it, the more it occured to me that there is a bit of a code smell about this approach. You inject a view into your Tab Region... some magic stuff happens and a tab gets added... you have to add some imcomprehensible dynamic binding to some XAML styling stored in a file somewhere and this may or may not set your header text. If any one single element of this is just a little bit wrong you won't get an error but it just won't work.
In my view this is both brittle (i.e. very easy to break) and pretty inpenetrable unless you have a deep understanding PRISM, the model, and of XAML. Fortunately there is a much nicer and simpler way to do this:
Simply create a view called TabRegionView which contains only a blank TabControl. You probably want to add this to your Shell.xaml. Create an Event called InjectTabView which has a Payload of type UserControl and subscribe to this event in your TabRegionView control. When the event fires in TabRegionView, you create the TabItem manually and add the view to the TabItem like so:
public void TabAdded(UserControl view)
{
var item = new TabItem();
item.Content = view;
item.IsSelected = true;
item.Header = "Header Text";
mainTab.Items.Add(item);
}
When you want to display a view as a new tab, your code looks something like this:
var view = new View(params);
_eventAggregator.GetEvent<InjectTabViewEvent>()
.Publish(view);
This will be picked up by TabRegionView and the view will be added as a new tab. You could easily wrap the View in harness of some type that contains header text, an image and bool to indicate whether or not the tab should be autoselected.
IMHO this technique has the dual advantages of giving you direct control of what is going on AND it is much easier to follow.
I'd be very interested to get an opinion on this from any PRISM officianados.

If you are using RegionManager, you need to Activate your view. There's likely some piece of code where you are adding the view to the region, you just additionally need to tell that region to activate it.
public void AddViewToRegion(IRegion region, object view)
{
region.Add(view);
region.Activate(view);
}
It seems silly, but you get the behavior you are seeing if you don't do this. I found this kinda frustrating, but it was easy enough to fix. The behavior is even stranger when you add multiple tabs if you don't at least Activate the first view you add.
My understanding is that if you don't do this, the view never becomes part of the visual tree (this is a side-effect of the fact that the TabControl deactivates (removes from the visual tree) when a tab isn't "in front". This is good for certain operations, but makes things like this be a little wonky.

Some random thoughts:
try to remove the style from TabControl
check visual tree with help of Snoop tool. You should see your TabItem with view's UserControl under the TabControl. Next you can check what's wrong with that UserControl and its children (your view's content). They are probably hidden for some reason.
other thing to think over - RegionAdapter. RegionAdapters are responsible for adapting regions' views to host UIControl.

Related

Accessing variables from XAML and object from ViewModel using Code Behind

I'm a newbie in windows phone development. I would like to ask if it is possible to do this scenario. I need to access a variable in XAML using my code behind, then I will add it as an item to my existing list found in my View Model. Therefore, I need to access both of my View Model to get the list and the XAML to get the variable from the resources.
Is this doable? If yes, how can I access it. This is what I have in my current XAML.
<phone:PhoneApplicationPage.Resources>
<system:String x:Key="scanName">SCAN</system:String>
</phone:PhoneApplicationPage.Resources>
Thanks much,
What you're trying to do is a pretty big violation of everything MVVM is about, but it is possible...
With the following lines in the codebehind of your view, you can...
...access the resource string:
var scanName = this.Resources["scanName"];
...access the ViewModel:
var vm = DataContext as MyViewModel;
if (vm == null) return;
vm.ScanHistory.Add(scanName);
That being said, you really shouldn't do this. The idea of MVVM is to decouple ViewModel and View completely and let the WPF binding mechanisms wire it together for you. In your case, as far as I can tell, you should store the scan name somewhere else, either as a resource or a config value, fetch it in your ViewModel and provide a property on your ViewModel to which your View can bind.
I haven't near winphone app so i make simple example on wpf(it's similiar with winphone).
//write string value from dynamic resource into textblock
<TextBlock FontSize="14" Text="{DynamicResource scanName}"/>
//changing resource in codebehind (this is Window in my example)
this.Resources["scanName"] = "new value";
As my mind you scenario is veru specific.Try to read about bindings. May be bindings will be more useful in your scenario.

Split pages and navigate in WPF

I'm trying to create a WPF page project, in which I've split the screen in two. On the left side, I have four clickable links / buttons.
Clicking on one of the links opens a corresponding page on the right side of the screen. There, options can be set. When a user uses the navigation bar on top of the screen, it should apply on the right side only.
Is this possible to do? What would be a better approach?
I'd like to know how to tell a page (part) which page to load. So that would make the right page dynamic?
Would it be better to split up the Grid? Or would a DockPanel be a better solution?
I've created a large Window in WPF, but I'd like to split up all these pages in a usefull navigation Page. So I have a bit of experience in using WPF and XAML. How should I approach this problem?
Firt you need to identify the components you want, mainly I see three components, the LeftSide navigation pane, the TopSide navigation pane and the MainContent pane. First lets talk about the MainContent pane, I think the best way for doing this is to use binding and date templates for making this dynamically. In your ViewModel, or DataContext, you need to have a property that represent the Content that you want to show in the MainContent, lets call it MainContent, then the MainContent View could be a ContentControl and set the property Content bindings to the ViewModel's MainContent property. In this way you only need to set the DataTemplate for each class item that you want to show. Other way could be to use a tab control and chage the ControlTemplate, this way is not dynamic because you need predefine all contents that you will show.
Now, for the navigation pane, you could use any control, for instance you could use a Radio button and change the ControlTemplate, and make the logic in the view model, using commands, for instance.
And now, the use of the Grid or DockPanel depends of what you want your application to do. If you want a dynamic width, you should to use a Grid with a GridSplitter, but if you want fixed width, you could to use a DockPanel due it is a bit more efficient/faster than the Grid.
Hope this answers helps, and not to be confused. Please feedback if any doubt.
EDIT
For instance, in the view model:
public class MainViewModel:INotifyPropertyChanged
{
public object Content {get; set;} //Here you must to raise the PropertyChanged event
private ICommand _showSummaryCommand
public ICommand ShowSummaryCommand
{
get { return new _showSummaryCommand ?? (_showSummaryCommand = new SomeCommandImplementation((sender,e)=>{Content = new SummaryViewModel()},true))} //most of commands implementations recive two parameters, an delegate to execute and a bool delegate for can excecute
}
}
and for the view, in some resource dictionary:
<DataTemplate TargetType="{x:Type ViewModels:SummaryViewModel}">
<DataGrid>
<!--Here goes what ever you want to show for instace-->
<TextBlock x:Name="descriptionText" Text={Binding Description}/>
</DataGrid>
</DataTemplate>
and in the place where you will to show all the contents
<!--....-->
<ContentControl Content={Binding Content}/>
<!--....-->
Hope this code helps a bit more :)

How can my design implement MVVM?

I have an issue trying to understand how to implement the MVVM pattern in my application. It's a small application and I will explain what it does.
My application creates a backup of files. The UI lets the user choose which folder they want to back up and where it should be backed up too. After making their selection they click the "start" button.
This then passes the folder source and folder destination to a class library (called backup.cs) which creates the back ups of all the files inside each folder. During this, a log (Log.cs) is created logging each stage and the state of each file it attempted to back up (complete, failed, other, etc). Now, the log is in memory only.
When the back up is complete I want a window to open (a view) which will display all the logs. It's at this point I can't understand how to use the MVVM pattern.
As it stands today, I pass my log (which holds the data in a hierarchical way) to my MainWindow's constructor and bind to the datacontext, using a treeview in my xaml I get the desired results. However, I now want to use MVVM.
My question is very similar to my previous question, as you can see the answer is to pass the log as a paramter to the ViewModel constructor. The issue is, I don't know how to do that and also display a window!
The only way (in my head) I can achieve this is by passing the Log as a parameter to a constructor of my View but this defeats the point of the MVVM. I could pass the parameter to my ViewModel's constructor (which would fit the MVVM pattern) but would that then mean I have to also create an instance of my View from my ViewModel constructor as well? Otherwise all I would do is create my ViewModel but have no way to display the results since the View isn't displayed.
I hope I have explained where I'm struggling clearly, can any one suggest a way forward please?
Most likely you 'll want the viewmodel to accept (and expose through a property) a collection such as a List<Log> -- typically this would be an ObservableCollection<Log>, but if the operation has already completed there is no real point in going that way. This is what you are describing as a possible solution.
To wire the viewmodel to the view, in essence you need to do this:
var viewModel = new LogsViewModel(...);
var view = new LogsView(); // no constructor parameters
view.DataContext = viewModel;
And finally you add view at some place of the application window's logical tree so that it gets displayed. MVVM frameworks automate this procedure, but you can also do it manually as simply as this.
Your view would then bind to the logs collection to display each log, perhaps using a DataTemplate:
<ItemsControl ItemsSource="{Binding Logs}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<!-- XAML to display each Log does here -->
<TextBlock Text="{Binding FileName}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
As an example, if you wanted that LogView being shown based on a button click in you main View.
public override void ShowCommandExecute()
{
var popup = new LogsView
{
WindowStartupLocation = WindowStartupLocation.CenterScreen,
DataContext = new LogsViewViewModel();
};
popup.ShowDialog();
}

How do you navigate a complex Visual Tree in order to re-bind an existing element?

In the above image, child is a ContentPresenter. Its Content is a ViewModel. However, its ContentTemplate is null.
In my XAML, I have a TabControl with the following structure:
<local:SuperTabControlEx DataContext="{Binding WorkSpaceListViewModel}"
x:Name="superTabControl1" CloseButtonVisibility="Visible" TabStyle="OneNote2007" ClipToBounds="False" ContentInnerBorderBrush="Red" FontSize="24" >
<local:SuperTabControlEx.ItemsSource>
<Binding Path="WorkSpaceViewModels" />
</local:SuperTabControlEx.ItemsSource>
<TabControl.Template>
<ControlTemplate
TargetType="TabControl">
<DockPanel>
<TabPanel
DockPanel.Dock="Top"
IsItemsHost="True" />
<Grid
DockPanel.Dock="Bottom"
x:Name="PART_ItemsHolder" />
</DockPanel>
<!-- no content presenter -->
</ControlTemplate>
</TabControl.Template>
<TabControl.Resources>
<DataTemplate DataType="{x:Type vm:WorkSpaceViewModel}">
....
WorkSpaceViewModels is an ObservableCollection of WorkSpaceViewModel. This code uses the code and technique from Keeping the WPF Tab Control from destroying its children.
The correct DataTemplate - shown above in the TabControl.Resource - appears to be rendering my ViewModel for two Tabs.
However, my basic question is, how is my view getting hooked up to my WorkSpaceViewModel, yet, the ContentTemplate on the ContentPresenter is null? My requirement is to access a visual component from the ViewModel because a setting for the view is becoming unbound from its property in the ViewModel upon certain user actions, and I need to rebind it.
The DataTemplate is "implicitly" defined. The ContentPresenter will first use it's ContentTemplate/Selector, if any is defined. If not, then it will search for a DataTemplate resource without an explicit x:Key and whose DataType matches the type of it's Content.
This is discussed here and here.
The View Model shouldn't really know about it's associated View. It sounds like there is something wrong with your Bindings, as in general you should not have to "rebind" them. Either way, an attached behavior would be a good way to accomplish that.
I think the full answer to this question entails DrWPF's full series ItemsControl: A to Z. However, I believe the gist lies in where the visual elements get stored when a DataTemplate is "inflated" to display the data item it has been linked to by the framework.
In the section Introduction to Control Templates of "ItemsControl: 'L' is for Lookless", DrWPF explains that "We’ve already learned that a DataTemplate is used to declare the visual representation of a data item that appears within an application’s logical tree. In ‘P’ is for Panel, we learned that an ItemsPanelTemplate is used to declare the items host used within an ItemsControl."
For my issue, I still have not successfully navigated the visual tree in order to get a reference to my splitter item. This is my best attempt so far:
// w1 is a Window
SuperTabControlEx stc = w1.FindName("superTabControl1") as SuperTabControlEx;
//SuperTabItem sti = (SuperTabItem)(stc.ItemContainerGenerator.ContainerFromItem(stc.Items.CurrentItem));
ContentPresenter myContentPresenter = FindVisualChild<ContentPresenter>(stc);
//ContentPresenter myContentPresenter = FindVisualChild<ContentPresenter>(sti);
DataTemplate myDataTemplate = myContentPresenter.ContentTemplate;
The above code is an attempt to implement the techniques shown on the msdn web site. However, when I apply it to my code, everything looks good, except myDataTemplate comes back null. As you can see, I attempted the same technique on SuperTabControlEx and SuperTabItem, derived from TabControl and TabItem, respectively. As described in my original post, and evident in the XAML snippet, the SuperTabControlEx also implements code from Keeping the WPF Tab Control from destroying its children.
At this point, perhaps more than anything else, I think this is an exercise in navigating the Visual Tree. I am going to modify the title of the question to reflect my new conceptions of the issue.

MVVM Passing data to dialog View Model

I'm looking into using MVVM and while I understand it for the most part, there is one thing I can't get my head around.
Imagine I have a View and ViewModel combination that show a list of foobars. When the user selects a foobar in the list and clicks the edit button I want the foobar to be shown in a popup dialog window so it can be edited. This dialog window (the view) will have its own associated ViewModel.
I understand that the button can be bound to a command on the list ViewModel, but from here how do I instantiate the foobar editor?
1) Do I have to send a message back to the View, which will open the dialog window? If so, doesn't this defeat the purpose of having the command?
2) How does the foobar get passed into the ViewModel for the editor? If it is by its constructor, doesn't this make it hard to declare the ViewModel in XAML?
I feel that this is the last piece of the puzzle that is preventing me from using MVVM and I'd really like to get a nice decoupled solution to this.
Thanks
Matt
I would perhaps do it in the following way:
The command attached to the edit button starts edit dialog, creating a custom ViewModel (VM) for it. The command itself should be perhaps either in the list's VM or in the Model (not quite sure).
Foobar edit dialog's VM gets a reference to the Foobar at its constructor.
The foobar is cloned and the clone is edited.
As soon as the user presses OK in the foobar edit dialog, the clone's values are written back to the original foobar in the VM (and the dialog is closed).
The need for a clone comes from the fact that the user doesn't want to see the changes in the foobar list until he accepts the changes in the edit dialog. If however online editing is okay, the clone is not needed.
The changes are propagated automatically.
PS: although I am MVVM's proponent, I am not sure that my solution is an orthodox one from pure MVVM point of view.
This article from codeproject shows a WPF Dialog control that does exactly what you need. The reason this implementation is necessary is that you can't put a Window inside the visual tree of any other control. Which means out of the box WPF doesn't let you create a dialog inside a window. So the above article creates a subclass ContentControl that creates a window.
Anyways, you put this in your FooBarList View
<dialog:Dialog Content="{Binding Path=DialogViewModel}" />
You make sure you have something like this in a resource dictionary somewhere:
<Style TargetType="{x:Type dialog:Dialog}">
<Style.Triggers>
<Trigger Property="HasContent" Value="True">
<Setter Property="Showing" Value="True" />
</Trigger>
</Style.Triggers>
</Style>
and just write something like this(For WPF to work you need to implement INotifyPropertyChanged):
public Class FooBarListViewModel
{
IList<FooBar> FooBarList {get;set;}
FooBar SelectedFooBar {get;set;}
ViewModelBase DialogViewModel {get;set;}
public EditFooBar(object param)
{
DialogViewModel = FooBar;
}
}
To link the View to edit the FooBar to the FooBar ViewModel just do something like this(preferably in the Application.Resources so it's global)
<DataTemplate DataType={x:Type vm:FooBarViewModel}>
<vw:FooBarView/>
</DataTemplate>
(Or optionally: Use an IValueConverter to Convert get your View from a ViewModel like this post shows)
And then you're set. May sound like a lot, but it really frees you up a lot.
What’s missing is a Controller which is responsible for the workflow of the ViewModels. The Controller creates the ViewModels and it passes the necessary data between the ViewModels.
The WPF Application Framework (WAF) project contains sample applications that show how this might work.

Resources