This project is read-only.

Problem on ModernTab with MVVM and ContentLoader

Aug 18, 2013 at 1:49 PM
Hi, I'm new in using this great project and try to migrate an existing wpf application to this modern ui style. I try to use the ModernTab with MVVM binding and ContentLoader as followed
<Grid>
        <mui:ModernTab SelectedSource="{Binding SelectedFile, Mode=TwoWay}" Links="{Binding Files}" Layout="List" >
            <mui:ModernTab.ContentLoader>
                <app:LoremLoader />
            </mui:ModernTab.ContentLoader>
        </mui:ModernTab>
    </Grid>
I use a ModelView as datacontext for the view / page with Files list and SelectFile property as followed:
        public Uri SelectedFile
        {
            get { return _selectedFile; }
            set
            {
                if (_selectedFile != value)
                {
                    _selectedFile = value;
                    OnPropertyChanged("SelectedFile");
                }
            }
        }
And I have a ContentLoader class in this form
public class LoremLoader: DefaultContentLoader
    {

        protected override object LoadContent(Uri uri)
        {
            return new Lorem();
        }
    }
Problem is that the ContentLoader will called only the first one I select a link. The SelectedFile setter is called every time, but not the Loader. Any idea whats wrong? Thanks in advanced.
Aug 18, 2013 at 8:28 PM
Edited Aug 18, 2013 at 8:28 PM
Do you navigate to a new uri? Could you otherwise create a repro project?
Aug 19, 2013 at 12:10 PM
Sorry for late response. I try to navigate to the same view with appending parameter like

/Views/Template/TemplateFiles.xaml#1-109.hst
/Views/Template/TemplateFiles.xaml#1-110.hst
/Views/Template/TemplateFiles.xaml#1-111.hst

I create Link objects for each link, insert file name as displayname and uri like above for source attribute like this
foreach (string file in files)
            {
                Files.Add(new Link()
                {
                    DisplayName = file,
                    Source = new Uri("/Views/Template/TemplateFiles.xaml#" + file, UriKind.Relative)
                });
            }  
Files is a LinkCollection in ModelView and binded to ModernTab as shown before. I would like to get the same behavior as in the modern app demo
<mui:ModernTab.Links>
                <mui:Link DisplayName="Lorem Ipsum 1" Source="/1" />
                <mui:Link DisplayName="Lorem Ipsum 2" Source="/2" />
                <mui:Link DisplayName="Lorem Ipsum 3" Source="/3"/>
                <mui:Link DisplayName="Lorem Ipsum 4" Source="/4" />
                <mui:Link DisplayName="Lorem Ipsum 5" Source="/5"/>
            </mui:ModernTab.Links>
For each link the contentloader will be called once. Perhaps the the Uri usage for the source attribute is wrong? Thanks in advanced.
Aug 22, 2013 at 3:57 PM
i have the same problem
you have to set ModernFrame's KeepContentAlive property to false and than recomplie FirstFloor.ModernUI project

if you don't want to do this try this:

class MainWindow : ModernWindow // your ModernWindow
{
   public MainWindow()
   {
        InitializeComponent();
        this.Loaded += MainWindow_Loaded;
    }

    void MainWindow_Loaded(object sender, System.Windows.RoutedEventArgs e)
    {

        ModernFrame contentFrame = base.GetTemplateChild("ContentFrame") as ModernFrame;
        contentFrame.KeepContentAlive = false;      
    }
}
Sep 7, 2013 at 10:26 AM
I have the same problem but the 2 method gived by @mcd wont work for me

@asto65 did you have found a solution ?
Sep 10, 2013 at 7:29 AM
Edited Sep 10, 2013 at 7:54 AM
kozw wrote:
Do you navigate to a new uri? Could you otherwise create a repro project?
Here is a example project with the bug
Src project
Sep 10, 2013 at 9:48 PM
The uri fragment (everything after the # character) is omitted in MUI when comparing uri's. Meaning that /page#1 and /page#2 are equal and will refer to the same /page. This behavior is needed in order to support IContent.OnFragmentNavigation on an existing page.

Use the uri query (? character), to make sure your page uris are unique. /page?1 and /page?2 are different uris
Sep 11, 2013 at 6:18 AM
Edited Sep 11, 2013 at 9:03 AM
Thanks, that solve my issue ;)

Edit, Its seems with (? char) OnFragmentNavigation is never called
    public partial class NewsControl : IContent
    {
        private bool called;

        public NewsControl()
        {
            InitializeComponent();
        }

        public void OnFragmentNavigation(FragmentNavigationEventArgs e)
        {
            called = true;
            Console.WriteLine("OnFragmentNavigation " + called);
            called = false;
        }

        public void OnNavigatedFrom(NavigationEventArgs e)
        {
            called = true;
            Console.WriteLine("OnNavigatedFrom " + called);
            called = false;
        }

        public void OnNavigatedTo(NavigationEventArgs e)
        {
            called = true;
            Console.WriteLine("OnNavigatedTo " + called);
            called = false;
        }

        public void OnNavigatingFrom(NavigatingCancelEventArgs e)
        {
            called = true;
            Console.WriteLine("OnNavigatingFrom " + called);
            called = false;
        }
    }
}
Output :

Navigating from '/Content/NewsControl.xaml?0' to '/Content/NewsControl.xaml?1'
OnNavigatingFrom True
OnNavigatedFrom True
OnNavigatedTo True

Frangment is handled by '#'
But how to pass fragment if my Uri is like *.xaml?xxxxx
Oct 18, 2013 at 12:33 AM
Like kozw said, '/Content/NewControl.xaml?0' and '/Content/NewControl.xaml?1' are two different pages. There is no fragment in theses urls so the OnFragmentNavigation event won't be fired.

If you need to call the same instance of a page with different parameters, use the fragment method (append your parameters with '#' at the end of your Uri).
If you need to call a new instance of a page, each with its own parameters, use the query method (append your parameters with '?' at the end of your Uri).
You can mix both techniques, for instance :
/Content/NewsControl.xaml?0#simple
will call a new NewsControl page to display item 0 in simple mode
/Content/NewsControl.xaml?0#detail
will call the same NewsControl page to display item 0 in detail mode
/Content/NewsControl.xaml?1#detail
will call a new NewsControl page to display item 1 in detail mode
and so on...

The fragment technique is already supported in ModernUi but not the query technique (using '?' in your Uris).
You will need to implement your own ContentLoader implementation.
For instance :
    public static class UriExtensions
    {
        public static Uri Split(this Uri uri, out string query, out string fragment)
        {
            query = null;
            fragment = null;
            if (uri == null) return null;
            var value = uri.OriginalString;

            var i = value.IndexOf('#');
            if (i != -1)
            {
                fragment = value.Substring(i + 1);
                value = value.Substring(0, i);
            }

            i = value.IndexOf('?');
            if (i != -1)
            {
                query = value.Substring(i + 1);
                value = value.Substring(0, i);
            }
            return new Uri(value, uri.IsAbsoluteUri ? UriKind.Absolute : UriKind.Relative);
        }
    }

    public class LoremLoader: DefaultContentLoader
    {
        protected override object LoadContent(Uri uri)
        {
            String query;
            String fragment;
            uri = uri.Split(out query, out fragment);
// at this point the query and fragment parts have been removed from the initial uri, you can now search the Content corresponding to your uri
            return new Lorem();
        }
    }
    public partial class NewsControl : IContent
    {
        public NewsControl()
        {
            InitializeComponent();
        }

        public void OnFragmentNavigation(FragmentNavigationEventArgs e)
        {
// here you may check the fragment to change the state of your current page
            DataContext.Mode = e.Fragment;
        }

        public void OnNavigatedFrom(NavigationEventArgs e)
        {
        }

        public void OnNavigatedTo(NavigationEventArgs e)
        {
            String query;
            String fragment;
            e.Source.Split(out query, out fragment);
// Here you may instantiate your datacontext with the query part as a parameter
            DataContext = new NewsVM(query);
        }

        public void OnNavigatingFrom(NavigatingCancelEventArgs e)
        {
        }
    }
Oct 18, 2013 at 2:27 AM
Edited Oct 18, 2013 at 2:28 AM
in fact, my problem was solved 20 min after my last post.
but i am using an other method

example:
public partial class NewsControl
{
    public NewsControl (string param)
    {
        InitializeComponent();
        DataContext = new NewsControlViewModel(param);
    }
}
internal class NewsLoader : DefaultContentLoader
{
    protected override object LoadContent(Uri uri)
    {
        var str = uri.ToString();
        var param = str.Replace("/Content/NewsControl.xaml?", string.Empty);
        return new NewsControl(param);
    }
}
public NewsControlViewModel(string param)
{
    currentNews = NewsService.GetNewsList().FirstOrDefault(n => n.News == param);
}
Oct 20, 2013 at 5:02 AM
in fact, my problem was solved 20 min after my last post.
Would you mind posting your updated project or a general sample?
I have the same problem and still cant get it to work correctly
Oct 20, 2013 at 9:04 AM
Oct 21, 2013 at 2:59 PM
thank you very much!
I was able to get it working in program using your sample :)
Nov 4, 2013 at 1:52 PM
Edited Nov 4, 2013 at 1:55 PM
@smokingfish

You can simply solve this problem in another way.
if the Loader is not called everytime, just do the loading things without it.
based on SheppeR's program, here's the code:

firist of all, declare a behavior in your xaml file:
xmlns:behavior:"clr-namespace:Test"

<i:Interaction.Behaviors>
  <behavior:NewsTabBehavior></behavior:NewsTabBehavior>
</i:Interaction.Behaviors>
and then in this behavior you can declare a event to notify when ModernTab.SelectedSource is changing:
public class NewsTabBehavior : Behavior<ModernTab>
{
  public static event EventHandler<UpdateUIEventArgs<string>> Selected;

  private ModernTab tab;

  protected override void OnAttached()
  {
            base.OnAttached();
            tab = AssociatedObject;
            tab.SelectedSourceChanged += new EventHandler<SourceEventArgs>(HandleSelectedSourceChanged);
  }

  private void  HandleSelectedSourceChanged(object sender, FirstFloor.ModernUI.Windows.Controls.SourceEventArgs e)
  {
           string selectedLink = tab.SelectedSource.ToString();

            if(selectedLink.Contains("x"))
            {
                UpdateUI.Post<string>("xxx", Selected, null);
            } // x is your param
             
            // you can do some other things
  }
}

public class UpdateUIEventArgs<T> : EventArgs
{
        public T Content
        {
            get;
            private set;
        }

        public UpdateUIEventArgs(T currentContent)
        {
            Content = currentContent;
        }
}

public static class UpdateUI
{
        public static void Post<T>(T content, EventHandler<UpdateUIEventArgs<T>> handler, object sender)
        {
            EventHandler<UpdateUIEventArgs<T>> securityhandler = Interlocked.CompareExchange(ref handler, null, null);

            if (securityhandler != null)
            {
                securityhandler(sender, new UpdateUIEventArgs<T>(content));
            }
        }
}
and next step, revise the Loader to return the object only once:
internal class NewsLoader : DefaultContentLoader
{
        private NewsControl newsControl;

        public NewsLoader()
        {
            newsControl = new NewsControl();
        }

        protected override object LoadContent(Uri uri)
        {
            return newsControl;
        }
}
at last, Handle the event in the NewsControlViewModel:
public NewsControlViewModel()
{
  NewsTabBehavior.Selected += new EventHandler<UpdateUIEventArgs<string>>(Display);
}

private void Display(object obj, UpdateUIEventArgs<string> e)
{
  switch (e.Content)
  {
                case "xxx":
                 // change your content in NewsControl.xaml by Binding
                 break;
  }
}
Regards