WPF application using MVVMCROSS - wpf

I recently came across MVVMCROSS which I think is a very good idea to solve cross platform issues. So I was tempted to port over an application I was working on and my entire libraries to make them more portable. Now I am trying ti bind my viewmodels to the views and I am facing several issues and questions...
I have seen the examples of how navigation works and how to start the first view which works fine. However, for my application I cannot use this.. My main view consists of a menu bar at the top of the window from where views can be called. The main window contains this :
<Grid Height="Auto" Width="Auto">
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Menu Name="MainMenu"
Height="25"
Width="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType={x:Type Grid}}}"
Grid.Row="0">
<MenuItem Header="Tools" Width="Auto">
<MenuItem Header="Solar estimator" HorizontalAlignment="Left" Width="Auto" Click="Menu_Click"/>
</MenuItem>
</Menu>
<DockPanel Grid.Row="1" Name="DockPanel" Height="Auto" Width="Auto">
</DockPanel>
So what I wish to do is that whenever I call a new view from the menu, I'll attach it to the dockpanel.
The function Menu_Click has this code :
EnergyNeedsEstimatorView estimatorView = new EnergyNeedsEstimatorView();
double estimatorViewHeight = estimatorView.Height;
double estimatorViewWidth = estimatorView.Width;
estimatorView.SetValue(UserControl.WidthProperty, double.NaN);
estimatorView.SetValue(UserControl.HeightProperty, double.NaN);
DockPanel.Children.Add(estimatorView);
DockPanel.Height = estimatorView.Height;
DockPanel.Width = estimatorView.Width;
this.Height = estimatorViewHeight + MainMenu.Height + SystemParameters.WindowCaptionHeight + SystemParameters.ResizeFrameHorizontalBorderHeight;
this.Width = estimatorViewWidth + SystemParameters.ResizeFrameVerticalBorderWidth + 10;
So view is displayed fine however, since I explicitly removed the IMvxAppStart part from my core library and removed the IMvxAppStart Resolve from DoSetup in my Desktop library, the link between the viewmodel and the view is no longer made. I suppose I could do like before and specify to my view that it's DataContext is the viewmodel and it would work like I did before using MVVMCross but I feel this is not the best approach.
So you probably are wondering, why not just add the menu bar to every view instead of trying to attach my views to a dockpanel? Well, I do not want to change my menu bars everywhere when I need to make a change. So I am trying to have a generic main window from where to display all views. Right now I am stuck and trying to figure out how I can do this with MVVMCross..
Any ideas?

If you ask the container to create you a View, then it automatically tries to Load a ViewModel for you - see the code in https://github.com/MvvmCross/MvvmCross/blob/v3.1/Cirrious/Cirrious.MvvmCross.Wpf/Views/MvxWpfViewsContainer.cs
So you could replace your new with a call to
car view = Mvx.Resolve<IMvxWpfViewsContainer>().CreateView(MvxViewModelRequest<MyViewModel>.GetDefaultRequest()):
However, normally in mvvmcross ViewModels and Applications don't talk directly to the Container - instead, they talk to a Presenter and it may be better to modify your code to use a presenter instead. Within this flow your menu would
- send the Presenter ViewModelRequest objects
- the Presenter then asks the Container for a View
- the Container loads the View and provides it with info on it's ViewModel (how this is done depends on the platform and on the View).
- the Presenter can then actually show the View on the display (normally a window) somehow.
In other frameworks, you may hear the term navigation service used instead of what Mvx calls Presenter

Related

MVVMCross using Frame to have multiple Views on the same View - Biding dosen't work

I'm trying to get a MVVMCross WPF application to run in a particular way, having multiple Views in the same "big view". Seems that Cross is not really made to be used like this, but let's try it anyways. So what I've done so far is using to add two Views into the same container "MainView".
A MainView with two other Views added as frames:
https://i.stack.imgur.com/kSXVA.png
<Frame Grid.Row="0" Grid.Column="0" Source="XMLFileView.xaml" Margin="5"/>
<Frame Grid.Row="0" Grid.Column="1" Source="BinaryFileView.xaml" Margin="5" />
This ugly thing is a MainView with two Views inside frames. The Views do work properly by themselves but when added through a Frame the "MVVM binding magic" is lost and View<->ModelView lose their connection.
How can I manually add multiple ModelViews to my MainView?
Or perhaps there's a better way to have multiple Views displayed together, suggestions?
Is XMLFileView.xaml an user control? If yes, u can just use directly the user control and set the DataContext, e.g.
<controls:XMLFileView DataContext="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext}" />
more information
If not, then it's better to use DataTemplate according to here

WPF - How to change design (position of elements) of UI

please how could I change the position of the elements in the UI, or choose a different design when the application loads?
It could be done using User Controls for each design, but the bad thing about this solution is that the same code will be repeated and I do not want that.
Please what would be the best practices to achieve this, it should be noted that the controls must have a name to use it in the code.
Thanks in advance.
Summary: This is what I want to achieve
In WPF the layout is all XAML. XAML can be stored in a resource dictionary. So once you determine what layout you want you can load the correct resource dictionary. Basically this pattern is exactly like people loading themes to change the colors of a UI etc.
I once had a situation similar to yours.
In your case, you should design your grid first as follows,
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding LeftButtonColWidth}"/>
<ColumnDefinition Width="{Binding RightMainPanelColWidth}"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height = "32"/>
<RowDefinition Height = "*"/>
<RowDefinition Height = "{Binding ightMainPanelBottomButtonRowHeight}"/>
</Grid.RowDefinitions>
Then add properties "LeftButtonColWidth", "RightMainPanelColWidth" and "RightMainPanelBottomButtonRowHeight" in viewmodel to control your layout based on some setting specified by end-users somewhere.
The above code is just for main container.
You also need a container grid for Buttons which should be designed as the main container grid using Binding property. In button container, you need bind Grid.Row and Grid.Column to properties "ButtonContainerRow" and "ButtonContainerCol" in ViewModel, they will be changed based on the some specific setting, when the app starts.
This is my solution. There must be other better solutions.
I hope someone can give me a solution only using xmal. That would be the perfect one.

How to create Carousel like control?

I need a "Сarousel like" control in my windows phone 7 APP, there will be list of images and I need to switch them like items in Pivot. What control should I use? Pivot is very similar, but I need footer in my application.
If you(or anyone else stumbeling into this question hunting for fancy UI elements) want it a bit more fancy you could alternatively use the Silverlight Flow Layouts Library which is a very powerfull carousel system with a dedicated WP7 binary. I have not used this on WP7 yet my self, but I have used it with WPF and it both looks good and performs well.
You can use a Pivot control where you hide the headers, I blogged about a simple solution to this problem here:
A Windows Phone 7 Slide View with Page Pips
You can add your fixed footer under the Pivot control as follows:
<!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="#EAE5C7">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<local:PivotLocationView Source="{Binding ElementName=pivot}"
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Margin="0,0,0,10"/>
<controls:Pivot Margin="0,-30,0,40"
x:Name="pivot">
<controls:PivotItem>
...
</controls:PivotItem>
<controls:PivotItem>
...
</controls:PivotItem>
<controls:PivotItem>
...
</controls:PivotItem>
</controls:Pivot>
<!-- your fixed footer goes here -->
<Grid x:Name="footer" Grid.Row="1">
</Grid>
</Grid>
You can try to use the control from this Codeplex project Silverlight Carousel Control
and hope it does not use any non-WP7 features.

Two-way databinding to ObservableCollection on Silverlight TreeView using DragDropTarget

Here's the question at its most basic: how do I listen for an update of what is changing in a TreeView control modified via a DragDropTarget?
So here's my deal: I have a TreeView that holds agenda items. All are of the same data type (WCFAgendaItem), and are loaded into a hierarchy with children expressed as a property ChildItems. The whole thing is wrapped up in an ObservableCollection and bound to the TreeView using MVVM Light. Works great to view. I also want users to be able to use drag and drop to reorder, reorganize and add new items to this agenda coming from a variety of other sources (one example is a ListView of image slides). All new items would also have the same data type of WCFAgendaItem, for consistency's sake and easy serialization.
Here's my issue: dragging and dropping works beautifully on the UI using the Toolkit's drag drop functionality. But I have no idea how to get the ViewModel to understand changes to the contents of the TreeView.
Code from the view (Agenda.xaml):
(up top)
<UserControl.Resources>
<AHHSTeam_SLClassroomManagerMVVM_Helpers_Converters:BooleanVisibilityConverter x:Key="BooleanVisibilityConverter"/>
<sdk:HierarchicalDataTemplate x:Key="hdtAgenda" ItemsSource="{Binding ChildItems, Mode=TwoWay}" >
<Grid HorizontalAlignment="Left">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding ImageThumbnailWidth}" />
<ColumnDefinition Width="250" />
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Source="{Binding ThumbnailURL}" Width="{Binding ImageThumbnailWidth}" Height="{Binding ImageThumbnailHeight}" Visibility="{Binding HasImage, Converter={StaticResource BooleanVisibilityConverter}}" >
<ToolTipService.ToolTip>
<Image Source="{Binding ResizedImageURL}" />
</ToolTipService.ToolTip>
</Image>
<TextBlock Grid.Column="1" Text="{Binding Title}" TextWrapping="Wrap" />
</Grid>
</sdk:HierarchicalDataTemplate>
<Style TargetType="sdk:TreeViewItem" >
<Setter Property="IsExpanded" Value="True" />
</Style>
</UserControl.Resources>
(later on)
<controlsToolkit:TreeViewDragDropTarget Grid.Row="1" Grid.Column="0" x:Name="ddtAgenda" AllowDrop="True"
HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" >
<sdk:TreeView Width="375" ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Visible" ItemsSource="{Binding DailyAgenda, Mode=TwoWay}" ItemTemplate="{StaticResource hdtAgenda}">
</sdk:TreeView>
</controlsToolkit:TreeViewDragDropTarget>
ViewModel code (AgendaViewModel.cs) --> I tried listening for CollectionChanged, so far that doesn't seem to work
(in constructor)
//add notification of agenda changes
DailyAgenda.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(DailyAgenda_CollectionChanged);
(event)
void DailyAgenda_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
System.Windows.MessageBox.Show("Daily agenda updated, now has " + e.NewItems.Count.ToString() + " top-level elements.");
}
Code from model (WCFAgendaItem.cs)
[ContentProperty("ChildItems")]
public partial class WCFAgendaItem: INotifyPropertyChanged
{
private ObservableCollection<WCFAgendaItem> _childItems = new ObservableCollection<WCFAgendaItem>();
public ObservableCollection<WCFAgendaItem> ChildItems
{
get
{
return _childItems;
}
set
{
_childItems = value;
}
}
...
I am pretty sure that I get that listening for CollectionChanged isn't right in any case, given that this data doesn't just change at the top level. I looked at EventToCommand in Blend (MVVM Light, remember) but the only TreeView-specific event appears to be SelectionChanged, which doesn't seem right either. I looked at putting an EventToCommand trigger on the TreeViewDragDropTarget, but aren't those methods about overriding how the UI interactions happen? I don't think INotifyPropertyChanged on WCFAgendaItem is right for this either: although I'm going to want that later for editing item titles, it doesn't seem like it'll help me when items get moved around.
Maybe what I'm looking for is a stretch, but what I really want to have happen is for Silverlight to understand that the databinding works both ways on the ordering and contents of the WCFAgendaItem collection, and do all the collection reworking itself based on UI interactions. Then I could just listen for an update event after the collection is reworked - after that I can just crawl the modified ObservableCollection bound to the TreeView, and flatten/serialize/update via WCF.
Failing the ideal situation: I'm willing to crawl TreeViewItems if need be, but even if that's what I need to do I'm stuck on when to do it. Plus I need a way to pass all that back to the ViewModel so I'm not writing code behind. Do I need to attach to Drop() and rework the dropping logic? I found several old articles about custom drag drop implementations starting from the Toolkit, but nobody mentions how to save out the modified TreeView, especially in an MVVM situation.
finally {
While typing this out I found this article which may be useful, though that's a fair amount of work in the ViewModel. This is promising and I'll investigate, but I'm still holding out hope for something simpler. Also it looks like the Toolkit events have changed a little since the article was written.
}
I also had issues implementing this type of DragDrop functionality. The root cause seemed to be that neither the ItemDragCompleted event (EventHandler) nor ItemDroppedOnSource (DragEventHandler) pass the index at which the item was dropped.
I ended up subclassing the DragDropTarget in order to expose the protected method:
int GetDropTargetInsertionIndex(TItemsControlType dropTarget, DragEventArgs args)
I then used an attached behavior to assume responsibility for inserting items at the specified index into to the underlying collections.
I'm afraid the underlying code is too expansive to include in a StackOverflow answer (mainly due to extensive decoupling) but it's on my list of subjects to blog about. In the mean time, I hope the information above helps, it was certainly the key to the solution for me.
Ian

Add PRISM Region Manager In Existing Navigation Window

We have a "legacy" WPF applicaton that is based on a NavigationWindow. The NavigationWindow has a fairly large ControlTemplate that houses a ContentPresenter as so:
<ControlTemplate>
....snip...
<ContentPresenter x:Name="PART_NavWinCP" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"/>
.....snip....
</ControlTemplate>
What we want to do is use that ContentPresenter as the first tab and dynamically add other tabs at run time. Like this:
<ControlTemplate>
....snip...
<TabControl Background="Transparent" cal:RegionManager.RegionName="MainRegion" Grid.ColumnSpan="2" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<TabItem Header="Nav Window Content">
<ContentPresenter x:Name="PART_NavWinCP" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"/>
</TabItem>
</TabControl>
.....snip....
</ControlTemplate>
Then our Modules grab the RegionName and insert their content dynamically. The issue seems to be that the PRISM region manager doesn't like that our code is in a ContentTemplate and cannot resolve the region. I have tried updating the RegionManager, adding the Region dynamically, just having a root tab control without the ContentPresenter, but I cannot get this to work. Any ideas?
Regions in templates are an issue - since templates are rendered after the initial content they arent 'controls' or even instances per-se and the region manager has no way to get handle on it. i would assume adding a region this way wouldnt be supported.
Now, staright up tab control w/ no templates I was able to get to work just fine but recall needing to write a content adapter that knew how to handle the target region type and registering that in the bootstrapper before i did the module loading.
So we got around this by chaning the NavigationWindow to a Frame and dropping the content in the frame. We need to do a bit of styling in order to make it look good; however, this is the only way to get around the use of a region in a NavigationWindow content template. We are now removing all code from the frame that was common to the app and will be needed by the Shell (since the NavigationWindow cannot become the Shell).

Resources