This project is read-only.

Get the index of a ModernLink when clicked on in a ModernTab

Sep 24, 2013 at 4:56 PM
Edited Sep 24, 2013 at 9:28 PM
Basically, what the title says.
MsgBox(mtCommands.SelectedIndex)
Or something similar. The reason why is because I am dynamically adding links, and reusing the same User Control as the SelectedSource for the ModernTab, for each Link. However, The content in the reused User Control will get changed based on what link is selected. The reason is, because this particular ModernTab is meant to support an infinite amount of Links based on an external variable, and I cannot create an infinite amount of User Controls.

Any suggestions?
Oct 14, 2013 at 8:52 PM
Still looking for a way to do this. Can't seem to find any workarounds yet :/
Oct 14, 2013 at 9:58 PM
The ModernTab.SelectedSource property is of type Uri. You'll first need to find the associated link, then you can use the LinkCollection.IndexOf to get the index of specified link. Something link this works;
private void ModernTab_SelectedSourceChanged(object sender, SourceEventArgs e)
{
  var links = ((ModernTab)sender).Links;

  // find link having selected source
  var link = this.modernTab.Links.FirstOrDefault(l => l.Source == e.Source);

  // get index of link
  if (link != null) {
    var index = this.modernTab.Links.IndexOf(link);
  }
}
Or in a single (rather unreadable) LINQ query
private void ModernTab_SelectedSourceChanged(object sender, SourceEventArgs e)
{
  var links = ((ModernTab)sender).Links;
  var index = links.Select((l, i) => new { l, i }).Where(z => z.l.Source == e.Source).Select(z => z.i).FirstOrDefault();
}
Oct 15, 2013 at 8:17 AM
Through testing, the following only seems to work if the URI of each Link is unique. Since I am reusing the same URI, is there any of way of doing this?
Oct 16, 2013 at 1:29 AM
In the past, when this issue arised, I've been able to assign the "Tag" property of a control that belongs to a collection when first adding it to the list. That way, I could read the value of that Tag property later on, during a selection changed event. For example:
for (i = 0; i <= 5; i++) {
    Windows.Controls.MenuItem item = new Windows.Controls.MenuItem();
    item.Name = "somename" + Convert.ToString(i);
    item.Header = "somename" + Convert.ToString(i);
    item.Tag = i;
    somecontrol.Items.Add(item);
}
The reason why I used the Tag property for this, was because the particular control I was working with did not have a SelectedIndex property. By reading from the Tag property, I could conclude it's position in the collection, since I dynamically added the control to the collection earlier.

The Link object doesn't seem to have such a property, so I explored into adding such a property to it through creating a custom class object, which inherited the Link object. Doing so added the property, however the SourceEventArgs doesn't support this; I would need to make a custom version of that also, so I could read the Tag property. When I tried doing that, I get an error from the SelectedSourceChanged event regarding to not having a compatible signature with the new SourceEventArgs I have created.

After that, I'm not sure which step to take. I thought I could create a custom property, set it while I was adding the links, and read from it later. However although that concept seems like it would work, I got stuck at the event part. Also, there may be a better alternative I'm unaware of.

Just trying to provide some insight on what I've tried, and the main goal I'm trying to accomplish.
Oct 17, 2013 at 1:22 AM
So after some more experimenting, I was able to use the standard SelectedSourceChanged event, and retrieve the custom tag info like so:
TxtCmdLink link = (TxtCmdLink)mtCommands.Links.FirstOrDefault(l => l.Source == e.Source);
However still did not resolve my problem; only works if the URIs are different.

So I've concluded I must come up with a new, custom event, to add onto a custom version of ModernTab. This event would need to fire when the selected item on the left in the list is changed, regardless or whether or not the source has changed. However, I'm at a loss at how to go about doing this, but I'll look into it some more. I can think of how to add the event so it would show up and everything in it's list of events, but creating a custom "event args" is something I don't have any experience with. I'll keep you updated with what happens. Any ideas or input would be great.
Dec 16, 2013 at 2:07 AM
Okay, so it seems I've bit off a little more than I could chew on this one. Tried every route I knew with custom classes and custom properties, inheritance, and event creation. I do know something of this nature will involve a custom class, but since the ModernLink does not have any of the standard System.Windows.FramewordElement, or System.Windows.UIElement event handlers, and I don't know how to add them without inheritance (my custom control is already inheriting ModernLink; multiple inheritances aren't allowed), I'm at a loss at what to do, as any clever work-arounds with the standard control events can't be done.

I need a way to detect when the selection of a link in a ModernTab changes; not the source of the modern tab. I am reusing the source of the same usercontrol, so this doesn't work (read above).

Does anyone know where to go from here?
Dec 19, 2013 at 10:21 PM
sooo... why not use fragment navigation if you are reusing the source?

i.e:

/content/myusercontrol.xaml#myidentifier1
/content/myusercontrol.xaml#myidentifier2

and then you can use the selected source binding in your VM and just simply parse out everything after the # to find you "index" of the tab selected. I use this approach LOTS in my apps and lets me use the ModernTab in places where I want the same user control and same view model.

use whats already given to you, and understand the fragment navigation, and you can accomplish everything.
Dec 20, 2013 at 9:43 PM
Will this still trigger the SourceChanged event that ModernTab uses?

If so, this may work. Also, Can I dynamically add new (fragments?) to a predefined user control via code? This is essential; I'm in a scenario where the tab count in ModernTab can change based on user input. Some short examples would be nice too. I'll research the topic further though, thanks for the input.
Dec 20, 2013 at 10:10 PM
Yes it should still trigger the event.

You should be able to dynamically add new entries into the ModernTab links (bind to a property in the VM)
Dec 21, 2013 at 5:20 AM
Great! Thanks, this solved a problem that has plagued me for quite awhile now. Now that I have a way to uniquely identify the URI of the user control, while at the same time always pointing to it, allows for this to work (kozw's example above now works for getting the index as well, in case anyone was wondering).

However, I ran into another snag. While the ModernTab event SourceChanged get's fired everytime, the UserContorl that Implements IContent, and the event OnNavigatedTo does not get fired everytime, however.

Is there a way to force this behavior? I want the UserControl to be re-loaded essentially, just as it would if the URI sources were actually different.
Dec 24, 2013 at 6:48 PM
I found by using the event OnFragmentNavigation by implementing IContent works.

One last thing. When switching between the tabs on the left, the smooth "slide-in" animation doesn't show (I assume because it's still the same source technically).

I've looked at invalidation techniques, the ModernTab.SelectedSource property, and others, but nothing seems to work. How can I manually activate that? Any thoughts?
Jan 2, 2014 at 10:29 PM
Edited Jan 2, 2014 at 10:43 PM
perhaps send a refresh navigation command to the ModernTab's frame, for example I do this quite often:

in my xaml:
<mui:ModernFrame x:Name="loadCaseFrame"
                         Grid.Row="0"
                         Source="{Binding LoadCaseSource}"
                         Margin="0,10,0,0" />
in my code behind:

public partial class SomeUserControl : UserControl, IFrameRefreshHelper
{
    private ModernFrame f;
    private void OnLoaded(object sender, RoutedEventArgs e)
     {
        var ndvm = (NodeDetailsViewModel)this.DataContext;
        ndvm.LoadCaseFrameRefresher = (IFrameRefreshHelper)this;

        f = NavigationHelper.FindFrame("loadCaseFrame", this);
    }

  #region IFrameRefreshHelper Members
  public void RefreshFrame()
  {
      NavigationCommands.Refresh.Execute(null, f);
  }
  #endregion
}
and in my VM:
public IFrameRefreshHelper LoadCaseFrameRefresher { get; set; }
so now I can, for example, respond to some ICommand in my VM and call upon my IFrameRefreshHelper abstraction like so:
public RelayCommand NextLoadCaseCommand { get; private set; }
        public void ExecuteNextLoadCase()
        {
            if (SelectedNode != null)
            {
                SelectedNode.ExecuteNextLoadCase();
                LoadCaseFrameRefresher.RefreshFrame();
            }
        }
OR you can send a Message from your VM to your view to perform the refresh. In either approach, (message vs setting an interface in the VM), it doesn't violate MVVM with specific UI (control) knowledge in your VM.
Feb 9, 2014 at 1:54 AM
I searched the assembly Firstfloor.ModernUI and the entire .NET 4.5 framework, and couldn't find a reference to IFrameRefreshHelper through the Object Browser. What assembly does this belong to?

Also, I found another way to do this, however the process seems a bit inefficient.

The control Control TransitioningContentControl has a property called "RestartTransitionOnContentChange" that once set to true, anytime the content is changed programmatically in this container, the control gets refreshed and animation replays, which ultimately solves the entire problem.

Would the way described above work more efficiently? If so, could you further explain where IFrameRefreshHelper comes from?

Thanks
Feb 10, 2014 at 5:42 PM
IFrameRefreshHelper is my own interface. Its simple:
 public interface IFrameRefreshHelper
    {
        void RefreshFrame();        
    }
Make your View implement this as I show above, and you're golden.