Future Desktop Blades

Jan 4, 2011 at 3:04 PM
Edited Jan 4, 2011 at 8:05 PM

I'm just discovering Silverlight and came across nRoute and Future Desktop.  I'm trying to play around with Future Desktop and, as is usual for a semi-n00b like myself, I've gotten stuck on what I know will be a very simple solution.  So I hope you can bear with me. ;)

When I startup the project in the browser three (3) Home Page blades show up.  I was scanning the code to figure out how it is that those 3 were created, but cannot figure that out.  Bouncing off of that, will I (and if I may ask, how can I) accomplish a few things:

1) I want to set the initial blades on startup.  Call them Home, Calendar, Tasks (3 total). I want them to be static blades that are always visible. The content inside would change based on updates, but they are like mini-dashboards.  Will I have to create separate blades that are outside of the navigation of Future Desktop? I do not want any of these to change if a link anywhere else is clicked.  And if an edit to content inside one is to be made, I want to open up a separate blade for the editing and commit, and then have the static blade it's related to update.

2) If I am on the first Home blade, and I click on one of the sidebar panel links (like "School Stuff"), it replaces the Home blade (although the other 2 Home blades remain.  Is it possible to create a new blade for such non-static links, so that "School Stuff" would open in its own blade. Essentially, I was hoping to get these non-static links to open in one blade, so that every time something is non-static it just replaces that one blade.  I like the functionality of replacing a non-static blade when clicking a link, however....

3) What if I want a non-static blade to remain open and not be overridden by clicking on a sidebar link?  Such that, lets say I am on the Task blade which could theoretically have a Sub-Task to it that I do in fact want to remain open when the user clicks that link.  The sub-task could also be non-static, but one level down, so that its not initially visible, but only when the user clicks on the link.  Now, I don't mean one single task (such as creating a task to get work done today in Outlook), by sub-task I mean just another sub-blade that drills down deeper into the Task blades functionality.

I appreciate your help.

Jan 4, 2011 at 4:09 PM

I found where the extra duplicate Home page blade workspaces were being created, in WorkspacesViewModel.cs (which is where I looked, but I was looking for something like an integer (3), instead of really examining the code (duh!)).

So I commented out the extra workspaces:

        private void SetupWorkspaces()

        {            

Workspaces = new ObservableCollection<Workspace>

                             {                                

new Workspace() { IsActive = true },                                 

//new Workspace(),

//new Workspace()

                             };            

ActiveWorkspace = Workspaces[0];

        }

I will conquer this beast and figure out how to add a new blade.

Coordinator
Jan 5, 2011 at 11:55 AM

Hi remesq, you are right, the workspaces are controlled/managed through the Workspaces-ViewModel class. Now, as for your specific needs, I would reckon abstract the workspaces as a stand-alone service so you can control the blades/workspaces from outside of the ViewModel. My approach was very generic/control-like, but you could enhance it to suit you needs. Secondly, adding a new workspace is as simple as inserting a Workspace type to the workspaces collection (see AddNewWorkspace method in the VM). And lastly, to make static or non-static blades, you'll need to enhance the Workspace model (like adding a IsStatic property) - and then make the UI behave per the model's state. Shouldn't be so hard.

Cheers,
Rishi 

Jan 6, 2011 at 9:57 PM

Thanks for the response.  I think I can at least attempt to handle your second suggestion. Bear in mind I am not a programmer. :)  You make it sound so easy, lol.

The first suggestion, however, I have no idea how to do.  I'll do some more research.  I get the concept, its the implementation that has me stumped.

Norm!

REM

Jan 12, 2011 at 1:37 PM
Edited Jan 12, 2011 at 1:39 PM

Rishi,

I am really stumped.  And I know it's a simple solution.  I've been trying for a few days (off and on) now to do what I first posted about but with no luck.  I added a new DocumentWorkspace.cs which is just a copy of Workspace.cs.  I also tried figuring out what to do with the WorkspaceViewModel.cs and the WorkspaceControl.xaml (and the WorkspaceControl.xaml.cs file) with no luck.  In the WorkspaceControl I got a new page to be shown, but it just overlaps the existing blade and is not a separate workspace blade.  I suspect that some work needs to be done there, but again, I cannot figure it out. :(

Here is the mess I made with WorkspaceViewModel (in it I tried a couple of different things to add a new DocumentWorkspace (for a separate static blade), some of which I commented out to try different things).  Just note that I left off where you see a comment to Rishi.  There I had an "ObservableCollection<DocumentWorkspace> DocumentWorkspaces" but changed it to "ObservableCollection<DocumentWorkspace> Workspaces" to see if it would work, but that just through an ambiguity error for all successive "Workspaces" in the code.  Also note that CodePlex code insertion wipes out the text inside "<>" so an ObservableCollection will appear not to have that (although I manually inserted it below where the comment to Rishi appears to explain):

 

using System.Collections.ObjectModel;
using nRoute.Components;
using nRoute.Samples.FutureDesktop.Model;
using nRoute.ViewModels;

namespace nRoute.Samples.FutureDesktop.ViewModels
{
    [MapViewModel(typeof(Workspaces))]
    public class WorkspacesViewModel
        : ViewModelBase
    {
        private Workspace _activeWorkspace;
        //private DocumentWorkspace _activeDocumentWorkspace;

        public WorkspacesViewModel()
        {
            SetupWorkspaces();
            //SetupDocumentWorkspaces();
            SetupCommands();
        }

        #region Properties

        public ObservableCollection<Workspace> Workspaces { get; set; }
        public ObservableCollection<DocumentWorkspace> Workspaces { get; set; } //NOTE: Rishi, this is where I left off, I originally had the collection as DocumentWorkspaces

        public Workspace ActiveWorkspace
        {
            get { return _activeWorkspace; }
            set
            {
                if (_activeWorkspace != value)
                {
                    _activeWorkspace = value;
                    NotifyPropertyChanged(() => ActiveWorkspace);
                }
            }
        }

        //public DocumentWorkspace ActiveDocumentWorkspace
        //{
        //    get { return _activeDocumentWorkspace; }
        //    set
        //    {
        //        if (_activeDocumentWorkspace != value)
        //        {
        //            _activeDocumentWorkspace = value;
        //            NotifyPropertyChanged(() => ActiveDocumentWorkspace);
        //        }
        //    }
        //}


        public ActionCommand AddNewWorkspaceCommand { get; private set; }

        public ActionCommand CloseActiveWorkspaceCommand { get; private set; }

        public ActionCommand SetActiveWorkspaceCommand { get; private set; }

        public ActionCommand SetActiveWorkspaceIndexCommand { get; private set; }

        #endregion

        #region Helpers

        private void SetupWorkspaces()
        {
            Workspaces = new ObservableCollection
                             {
                                 //Took out exta new Workspace(),
                                 new Workspace() { IsActive = true },
                                 //new Workspace(), 
                                 //new Workspace(),
                             };
            ActiveWorkspace = Workspaces[0];
            }
        //private void SetupDocumentWorkspaces()
        //{
        //    DocumentWorkspaces = new ObservableCollection
        //        {
        //            new DocumentWorkspace() { IsActive = true },
        //        };
        //    ActiveDocumentWorkspace = DocumentWorkspaces[0];
        //}
        private void SetupCommands()
        {
            AddNewWorkspaceCommand = new ActionCommand(AddNewWorkspace, () => this.Workspaces.Count < 9)
            .RequeryWhenExecuted()
            .RequeryOnCollectionChanged(this.Workspaces);

            CloseActiveWorkspaceCommand = new ActionCommand(CloseActiveWorkspace, () => this.Workspaces.Count > 1)
            .RequeryWhenExecuted()
            .RequeryOnCollectionChanged(this.Workspaces);

            SetActiveWorkspaceCommand = new ActionCommand(SetActiveWorkspace);

            SetActiveWorkspaceIndexCommand = new ActionCommand((i) =>
            {
                if (i < 0 || i >= this.Workspaces.Count) return;
                SetActiveWorkspace(this.Workspaces[i]);
            });
        }

        private void AddNewWorkspace()
        {
            //var _documentworkspace = new DocumentWorkspace();
            //this.DocumentWorkspaces.Add(_documentworkspace);
            //SetActiveWorkspace(_documentworkspace);

            if (this.Workspaces.Count >= 9) return;

            var _workspace = new Workspace();
            this.Workspaces.Add(_workspace);
            SetActiveWorkspace(_workspace);
        }

        private void CloseActiveWorkspace()
        {
            if (this.Workspaces.Count <= 1) return;

            var _currentActiveWorkspace = this.ActiveWorkspace;
            var _workspaceIndex = this.Workspaces.IndexOf(_currentActiveWorkspace);
            _workspaceIndex = (_workspaceIndex == 0) ? 1 : _workspaceIndex - 1;

            SetActiveWorkspace(Workspaces[_workspaceIndex]);
            Workspaces.Remove(_currentActiveWorkspace);
        }

        private void SetActiveWorkspace(Workspace workspace)
        {
            if (workspace == null || object.ReferenceEquals(workspace, ActiveWorkspace)) return;
            ActiveWorkspace.IsActive = false;
            workspace.IsActive = true;
            ActiveWorkspace = workspace;
        }

        #endregion

    }
}

 

I know your a busy guy (just from checking what you post here and on orktane.com), but I appreciate any help or guidance.

Coordinator
Jan 14, 2011 at 9:38 AM

Let me come back to you over the weekend - as I need to dive into the code. 
Rishi 

Feb 5, 2011 at 2:17 PM

Rishi,

I see you've been very busy with nRoute, but I just wanted to follow up and ask if this were still possible.  Thanks.

-REM

 

Coordinator
Feb 5, 2011 at 5:27 PM

Just to refresh, the goal was to create sticky blades?

Rishi

Feb 5, 2011 at 8:50 PM

Rishi,

Thanks.  Yes, that was one of the goals: to create a sticky (or static) blade.  I wanted to create multiple static blades.

The other aspect was to open a generic blade if, for example, a link was clicked on the right menu, so that page opens up in a new blade, rather than in one of the static ones.

My goal is to always have the static blades content there, so that the user is not clicking "back" to get to a particular blade's main page.  So when someone clicks a link in the static blade, it opens up a new generic blade.

Sorry if I am not making sense.  I appreciate your help.  Let me know if I can return the favor.

-remESQ

Coordinator
Feb 8, 2011 at 3:16 AM

Alright, so in the repository I've added a sample project to nRoute 0.5's sample projects (see nRoute5_Samples/Desktop/FutureDesktop/) - note this is a WPF port of the SL project.

So, as for the features - first, I added the sticky blades by doing two things:

  1. Added a "IsSticky" property to the Workspace type
  2. Then I updated the CloseWorkspaceCommand to only allow it's execution for non-sticky workspaces:

                CloseActiveWorkspaceCommand = new ActionCommand(CloseActiveWorkspace, 
                    () => this.Workspaces.Count > 1 && !this.ActiveWorkspace.IsSticky)
                .RequeryWhenExecuted()
                .RequeryOnPropertyChanged(this, () => ActiveWorkspace)
                .RequeryOnCollectionChanged(this.Workspaces);
     See the !IsSticky and requery setup when the active-workspace changes

Secondly, to open new blades, I did three things:

  1. Created a ProxyNavigationHandler type, that implements INavigationHandler - what this does is it raises a "NavigationRequested" event when a navigation request comes in, and then using a slight delay it defers the navigation request to the default navigation handler
  2. Then in the WorkspacesViewModel, I registered an instance of the proxyhandler, using the handler name "EmptyBladeHandler":

    _emptyBladeHandler = new ProxyNavigationHandler();
    _emptyBladeHandler.NavigationRequested += (s, e) => AddNewWorkspace();
    NavigationService.RegisterNavigationHandler("EmptyBladeHandler", _emptyBladeHandler);
    
     
    Note, this gives us the indication for a new navigation request that has to be handled in a new blade - so accordingly I just add a new workspace. However, note this is a very simple way to do this, but there might be more elegant / better ways to achieve the same thing
  3. I updated two of the links NavigateAction behaviors (from the SidePanel) to target the "EmptyBladeHandler" named handler:

    <i:Interaction.Triggers>
          <i:EventTrigger EventName="MouseLeftButtonUp">
                  <n:NavigateAction Url="Pages/FuturePage/WilsonPassContactPage/" HandlerName="EmptyBladeHandler"/>
          </i:EventTrigger>
    </i:Interaction.Triggers>
    
     
    Basically, this is now sends the navigation request to our registered proxy handler, using which we are able to open a new blade, and then handle the response in the newly open blades's navigation container. 

Hope this helps, and as for returning the favor - well, just help out with nRoute's community when you can, kind of pass-on the favor :)
Rishi 

Feb 16, 2011 at 11:28 AM

Rishi,

No need to reply.  Been away for a bit.  Implemented this and it works great.  Just wanted to thank you for the hard work at helping to resolve this.

-REM