none
How to reffer XML data to IValue Converter?

    Question

  • I am trying to build navigation model where pages are loaded into frame by selection listbox items of the ListBox and also by clicking 'back' / 'forward' buttons. Listboxitems with UriSource associated are generated from XML. While cliking 'back' / 'forward' buttons, I need to make the selected item in the ListBox be the current with the page loaded in the ContentFrame. As a result, I need to pass two values in the Convert method, one is the current source of the frame, another is the items collection of the ListBox, so that I can return the selected index of the item in the ListBox according to these two values. The Value Converter I provide is good for listboxitems defined in the C# code. I need to reffer data from XML to Value Converter. Any ideas are highly appreciated.
     

    <Window.Resources> 
      <DataTemplate x:Key="pageTemplate" > 
      <StackPanel Orientation="Horizontal" > 
       <TextBlock Text="{Binding XPath=name}" FontSize="14" VerticalAlignment="Center" Margin="4" /> 
      </StackPanel> 
     </DataTemplate> 
    
     <XmlDataProvider x:Key="PagesData" XPath="Pages"> 
    
      <x:XData> 
       <Pages xmlns=""> 
        <page id="page01"> 
         <name>Page 1</name> 
         <source>Pages/Page1.xaml</source> 
        </page> 
        <page id="page02"> 
         <name>Page 2</name> 
         <source>pages/Page2.xaml</source> 
        </page> 
       </Pages> 
      </x:XData> 
     </XmlDataProvider> 
     <local:IndexUriSourceConverter x:Key="indexConverter" /> 
    
    </Window.Resources> 
    <StackPanel> 
     <ListBox Name="listbox" 
     ItemsSource="{Binding}" 
     SelectedIndex="{Binding ElementName=frame, Path=., Converter={StaticResource indexConverter}}">  
     </ListBox> 
     <Frame Name="frame" Source="pages/Page3.xaml" Tag="{Binding ElementName=listbox, Path=Items}" Navigating="frame_Navigating"></Frame> 
     <Button Click="Button_Click">Go</Button> 
    </StackPanel>
    
    

    Code behind:

    private void Button_Click(object sender, RoutedEventArgs e) 
    
     { 
      frame.Source = new Uri("pages/Page3.xaml", UriKind.RelativeOrAbsolute); 
     } 
    
    
     private void frame_Navigating(object sender, System.Windows.Navigation.NavigatingCancelEventArgs e) 
    
     { 
      BindingExpression be = listbox.GetBindingExpression(ListBox.SelectedIndexProperty); 
      be.UpdateTarget(); 
    
     } 
    
    

    Value Converter I tried. I believe I incorrectly trying to reffer to XMl:

     public class IndexUriSourceConverter : IValueConverter
       {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
         XmlElement element = value as XmlElement;
    
         if (element != null)
         {
          string uriSource = element.SelectSingleNode("source").InnerText;
          return new Uri(uriSource, UriKind.Relative);
         }
    
         var frame = value as Frame;
         if (frame != null)
         {
          var uriSource = frame.Tag as ItemCollection;
          if (uriSource != null)
          {
           return uriSource.IndexOf(frame.Source.ToString());
          }
         }
         return null;
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
         throw new Exception("The method or operation is not implemented.");
        }
       }
    Thank you!
    Thursday, September 09, 2010 6:14 AM

Answers

  • Hi Vladc77,

    I do have a button which acts very similar to yours in the code I have posted, and the ListBox will update if you click on the button.

    The reason why your button doesn't work is because the uri string is different from the one in XML. It's "Pages/Page2.xaml" in xml and "pages/Page2.xaml" in button. You can change the uri string in the Button to be exactly same as the one in XML file. Or you can change all OriginalSource node to lower cases and convert the Uri string to lower case in Convert method as following.

    xml:

        <XmlDataProvider x:Key="PagesData" XPath="Pages"> 
          <x:XData> 
            <Pages xmlns=""> 
              <page id="page01"> 
                <name>Page 1</name> 
                <Urisource>pages/page1.xaml</Urisource> 
              </page> 
              <page id="page02"> 
                <name>Page 2</name> 
                <Urisource>pages/page2.xaml</Urisource> 
              </page> 
            </Pages> 
          </x:XData> 
        </XmlDataProvider> 
    

    Convert method:

    		public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    		{
    			var uri = value as Uri;
    			if (uri != null)
    			{
    				string p = ";component/";
            return uri.OriginalString.Substring(uri.OriginalString.IndexOf(p) + p.Length).ToLower();
    			}
    			return null;
    		}
    

     Hope this helps.

    Best regards,

    Min


    Please remember to click “Mark as Answer” on the post that helps you, and to click “Unmark as Answer” if a marked post does not actually answer your question. This can be beneficial to other community members reading the thread.
    • Marked as answer by vladc77 Thursday, September 16, 2010 8:09 PM
    Tuesday, September 14, 2010 4:31 AM
    Moderator

All replies

  • Hi Valac77,

    I have been playing around with your code for a while and finally get it working.

    There are a few issues in your original code. Some of them might be just edit errors while posting. Here is some key points:

    If you bind ListBox.SelectedIndex to the Frame, you will not be able to ConvertBack. Which means you will not be able to Click on the ListBox to navigate anymore. To solve this, I bind ListBox.SelectedValue to Frame.Source instead.

    The main reason your original code doesn't work is because uriSource is a collection of XmlElement. So uriSource.IndexOf(frame.Source.ToString()); will always return -1;

    Please see the following code for details.

    xaml:

      <Window.Resources>
        <DataTemplate x:Key="pageTemplate" >
          <StackPanel Orientation="Horizontal" >
            <TextBlock Text="{Binding XPath=name}" FontSize="14" VerticalAlignment="Center" Margin="4" />
          </StackPanel>
        </DataTemplate>
    
        <XmlDataProvider x:Key="PagesData" XPath="Pages/page">
          <x:XData>
            <Pages xmlns="">
              <page id="page01">
                <name>Page 1</name>
                <source>Pages/Page1.xaml</source>
              </page>
              <page id="page02">
                <name>Page 2</name>
                <source>pages/Page2.xaml</source>
              </page>
              <page id="page03">
                <name>Page 3</name>
                <source>pages/Page3.xaml</source>
              </page>
            </Pages>
          </x:XData>
        </XmlDataProvider>
        <local:IndexUriSourceConverter x:Key="indexConverter" />
      </Window.Resources>
      <StackPanel>
        <ListBox Name="listbox" 
     ItemsSource="{Binding Source={StaticResource PagesData}}" SelectedValuePath="source" 
     SelectedValue="{Binding ElementName=frame, Path=.Source, Converter={StaticResource indexConverter}}">
        </ListBox>
        <Frame Name="frame" Source="pages/Page3.xaml" Tag="{Binding ElementName=listbox, Path=Items}"/>
        <Button Click="Button_Click">Go</Button>
      </StackPanel>
    

    C#:

        private void Button_Click(object sender, RoutedEventArgs e)
        {
          frame.Source = new Uri("pages/Page2.xaml", UriKind.RelativeOrAbsolute);
        }
    

     

    public class IndexUriSourceConverter : IValueConverter
      {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
          var uri = value as Uri;
          if (uri != null)
          {
            string p = ";component/";
            return uri.OriginalString.Substring(uri.OriginalString.IndexOf(p) + p.Length);
          }
          return null;
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
          var path = value as String;
          if (path != null)
          {
            return new Uri(path, UriKind.Relative);
          }
          return null;
        }
      }
    

    Hope this helps.

    Please feel free to let me know if you have any doubts or concerns about this issue.

    Best regards,

    Min


    Please remember to click “Mark as Answer” on the post that helps you, and to click “Unmark as Answer” if a marked post does not actually answer your question. This can be beneficial to other community members reading the thread.
    Friday, September 10, 2010 8:30 AM
    Moderator
  • It is great. Thank you for looking into my code. I applied this technique and it works great except the one functionality. I needed to update selected state of the listbox item associated with its content which will be loaded into frame and not necessary only by clicking the listbox items. My navigation model should allow loading pages from other controls and indicating it in the listbox. 

    For example, if I click the button to load Page2.xaml, not listbox item, then Page2.xaml is successfully loaded but selected state of listboxitem associated with Page2.xaml, which is second item in the listbox, will not be switched into selected state. As a result, I am looking for mechanism which will enable to indicate selected state of the appropriate listbox item based on what is loaded into frame.

     

    Friday, September 10, 2010 10:13 PM
  • Hi Vladc77,

    Based on my understanding, I think you want to achieve the following two functionality with this ListBox. Please correct me if I am wrong.

    a) Navigate to the corresponding page when click on a ListBoxItem.

    b) Update the SelectItem of the ListBox when Frame.Source changes.

     

    The code in my last post achieves these two functionalities. The ConvertBack method of the converter is targeting functionality b. And the Convert method is targeting functionality a. I have tested it on my mechine and it works fine. Please let me know if it doesn't work on your side so I can look into it.

    For example, you can click on the ListBox to navigate among pages. Then, if you click on Back or Forward button, you would see the ListBox will sync with the current page. It should work fine with the original sample code in my last post, and it is possible that it may not work for different kinds of URIs. The idea to achieve functionality a is to convert Frame.Source to a string of same format as in XML file. It will make the ListBox to update it's selected item based on it's SelectedValue. If it doesn't work for your application, you could set a break point in Convert method and find out why it doesn't convert the URI to the correct format. Then you can adjust the convert method and adpat it to your specific needs.

    Please feel free to let me know if you still have any doubts or concerns about this issue.

    Best regards,

    Min


    Please remember to click “Mark as Answer” on the post that helps you, and to click “Unmark as Answer” if a marked post does not actually answer your question. This can be beneficial to other community members reading the thread.
    Monday, September 13, 2010 1:59 AM
    Moderator
  • Thank you again. Your understading is correct except that I also need to use other controls to navigate through pages which are not withing navigation class scope. I decided to share a testing solution with you. Here is the link:

    https://cid-0c29483cf3a6a14d.office.live.com/self.aspx/WPF%5E_Tests/%5EFUriTest2.zip

    If you click the button which is not journals's back/forward buttons, the selected state of listboxitem will not be changed according to the page loaded into frame. I hope it is better to look into the solution than confuse you more.

    Monday, September 13, 2010 5:04 PM
  • Hi Vladc77,

    Thanks for sharing a sample.

    Encounter an error while clicking on your link. Would you please ensure this file is not private? Thank you.

    Best regards,

    Min


    Please remember to click “Mark as Answer” on the post that helps you, and to click “Unmark as Answer” if a marked post does not actually answer your question. This can be beneficial to other community members reading the thread.
    Tuesday, September 14, 2010 1:50 AM
    Moderator
  • I just realized that I did not set permssions for availability. Now, it should be available. Thank you.

    Tuesday, September 14, 2010 2:48 AM
  • Hi Vladc77,

    I do have a button which acts very similar to yours in the code I have posted, and the ListBox will update if you click on the button.

    The reason why your button doesn't work is because the uri string is different from the one in XML. It's "Pages/Page2.xaml" in xml and "pages/Page2.xaml" in button. You can change the uri string in the Button to be exactly same as the one in XML file. Or you can change all OriginalSource node to lower cases and convert the Uri string to lower case in Convert method as following.

    xml:

        <XmlDataProvider x:Key="PagesData" XPath="Pages"> 
          <x:XData> 
            <Pages xmlns=""> 
              <page id="page01"> 
                <name>Page 1</name> 
                <Urisource>pages/page1.xaml</Urisource> 
              </page> 
              <page id="page02"> 
                <name>Page 2</name> 
                <Urisource>pages/page2.xaml</Urisource> 
              </page> 
            </Pages> 
          </x:XData> 
        </XmlDataProvider> 
    

    Convert method:

    		public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    		{
    			var uri = value as Uri;
    			if (uri != null)
    			{
    				string p = ";component/";
            return uri.OriginalString.Substring(uri.OriginalString.IndexOf(p) + p.Length).ToLower();
    			}
    			return null;
    		}
    

     Hope this helps.

    Best regards,

    Min


    Please remember to click “Mark as Answer” on the post that helps you, and to click “Unmark as Answer” if a marked post does not actually answer your question. This can be beneficial to other community members reading the thread.
    • Marked as answer by vladc77 Thursday, September 16, 2010 8:09 PM
    Tuesday, September 14, 2010 4:31 AM
    Moderator
  • Thank you for looking again. For some reason, it did not work either.

    I used this converter which does the job:

    public class IndexUriSourceConverter : IValueConverter 
    	{ 
    		public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 
    		{ 
    		
    			return value;
    		} 
    	 public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 
            {
                if (value != null)
          {
            var uri = (Uri)value;
            var uriString = uri.OriginalString;
    
            if (uri.OriginalString.Contains(";component/"))
            {
              uriString = uriString.Substring(uriString.IndexOf("/") + 1);
            }
    
            return uriString;
          }
    
          return Binding.DoNothing;
            } 
    
    	}
    

    I highly appreciate you willing to help.

     

    Thursday, September 16, 2010 8:09 PM