0
votes

I'm in the middle of creating an application that sends API calls and saves the results to a database. In order to avoid getting back to the API documentation all the time I've created a small application to remind me of the basic elements of the calls. It works by loading an xml document into memory and using a listbox to pick the appropriate call. Then its elements are displayed in textblocks (and a textbox).

The structure of the file "apicalls.xml" goes like this:

<Calls>
<Call>
    <Description>blah blah</Description>
    <api>POST /api/something/somethingElse/etc</api>
    <Accept>application/xml</Accept>
    <ContentType>application/xml</ContentType>
    <Parameters>id</Parameters>
    <Method>POST</Method>
    <ReceiveFile>true</ReceiveFile>
    <Category>aCategory</Category>
</Call> </Calls>

There are a lot of "Call" elements. So the C# code is as follows:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Xml;
using System.Windows.Threading;

namespace ShowAPI
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    public XmlDocument doc = new XmlDocument();

    public MainWindow()
    {
        InitializeComponent();
        doc.Load("apicalls.xml");
    }

    private void getAll(string name)
    {
        XmlNodeList nodeList = doc.DocumentElement.SelectNodes("Call");
        int i = 0;
        foreach (XmlNode node in nodeList)
        {
            XmlNode cat = node.SelectSingleNode("Category");
            if (cat.InnerText == name)
            {
                ListBoxItem item = new ListBoxItem();
                item.Content = (++i).ToString() + " " + node.SelectSingleNode("api").InnerText;
                //item.Content = node.SelectSingleNode("api").InnerText;
                item.Name = "N" + i.ToString();
                list.Items.Add(item);
            }

        }
    }

    private void RadioButton_Checked(object sender, RoutedEventArgs e)
    {
        string name = (((RadioButton)sender).Content.ToString());
        list.Items.Clear();
        getAll(name);

        dispatch(); // this is to display the items after the listbox has been populated

    }

    private void dispatch()
    {
        list.Dispatcher.Invoke(DispatcherPriority.Render, EmptyDelegate);
    }


    private static Action EmptyDelegate = delegate () { };


    private void list_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (list.Items.Count == 0)
            return;

        int index = list.SelectedIndex;
        object sel = e.AddedItems[0];
        string s = sel.ToString();
        XmlNodeList nodes = doc.DocumentElement.SelectNodes("Call");

        IEnumerable<RadioButton> enumerable = panel.Children.OfType<RadioButton>();
        Func<RadioButton, bool> func = (x) => x.IsChecked == true;
        RadioButton res = enumerable.Single<RadioButton>(func);

        string find = res.Content.ToString();
        List<XmlNode> nodeList = new List<XmlNode>();
        foreach (XmlNode xnode in nodes)
        {
            XmlNode api_node = xnode.SelectSingleNode(".//api");
            XmlNode cat_node = xnode.SelectSingleNode(".//Category");
            string s_api = api_node.InnerText;
            string s_cat = cat_node.InnerText;

            if (s_cat == find)
            {
                nodeList.Add(xnode);
            }

        }

        XmlNode xxx = nodeList[index];
        description.Text = xxx.SelectSingleNode(".//Description").InnerText;
        api.Text = xxx.SelectSingleNode(".//api").InnerText.TrimStart(new char[] { 'G', 'E', ' ', 'T', 'L', 'U', 'P', 'S', 'O', 'D' });
        accept.Text = xxx.SelectSingleNode(".//Accept").InnerText;
        contentType.Text = xxx.SelectSingleNode(".//ContentType").InnerText;
        method.Text = xxx.SelectSingleNode(".//Method").InnerText;
        expectFile.Text = xxx.SelectSingleNode(".//ReceiveFile").InnerText;
        parameters.Text = xxx.SelectSingleNode(".//Parameters").InnerText;


    }

}

}

and this is the ListBox implementation in xaml:

<ListBox Name ="list" HorizontalAlignment="Left" Height="396" Margin="582,52,0,0" VerticalAlignment="Top" Width="561" SelectionChanged="list_SelectionChanged">
        <ListBox.Background>
            <RadialGradientBrush Center="0.1,0.5" GradientOrigin="2,1" RadiusX="1" RadiusY="0.3" SpreadMethod="Reflect">
                <GradientStop Color="#FFC7431C" Offset="1"/>
                <GradientStop Color="#FFD31010" Offset="0.494"/>
            </RadialGradientBrush>
        </ListBox.Background>
    </ListBox>c

So the listbox is populated normally, I can see all the items fine but when I move the mouse over them, any item beyond the 12th cannot be selected (they don't seem to be interactive - the background does not change when moving over the item).

I've tried inserting the listboxitems as objects instead of text (I've read somewhere about it) and avoided to insert identical items by adding an index number and the method name before the actual content. Nothing seems to work and I have no clue...

P.S. I also used reflection to GetProperties() and compare the values of the first listboxitem with the values of the 14th listboxitem and they were identical besides the name and content. I just noticed that selecting the item with the arrow keys works fine. It's the mouse selection that won't work for any item after the 12th.

2
Is there any particular reason why you manually create ListBoxItems, instead of just adding the content object to the Items collection? Or even better, assign or bind a collection of item objects to the ItemsSource property of the ListBox.Clemens
@Clemens I only did so because I've found on the internet that adding the content only may be the reason that the listbox items could not be selected. The initial implementation is included in the code as a comment: item.Content = node.SelectSingleNode("api").InnerText;pzogr
That would still require to create a ListBoxItem manually. Why not list.Items.Add(node.SelectSingleNode("api").InnerText)?Clemens
@Clemens Sorry! My mistake... that's what I had used initially but it makes no difference...pzogr
Is there anything in your view that overlays the ListBox?mm8

2 Answers

0
votes

It seems like there is some element that overlays the ListBox, like for example a TextBlock. That's why you can't select the items that are located "under" the overlaying element.

Remove it and the selection should work as expected.

0
votes

I propose you to use slightly different approach based on WPF core features such as XmlDataProvider and current item synchronization.
So first define a XmlDataProvider in your window resources:

<Window.Resources>
    <XmlDataProvider x:Key="CallsData">
        <x:XData>
            <Calls xmlns=''>
                <Call>
                    <Description>blah blah 3</Description>
                    <api>POST /api/something</api>
                    <Accept>application/xml</Accept>
                    <ContentType>application/xml</ContentType>
                    <Parameters>id</Parameters>
                    <Method>POST</Method>
                    <ReceiveFile>true</ReceiveFile>
                    <Category>Cat1</Category>
                </Call>
                <Call>
                    <Description>blah blah 2</Description>
                    <api>POST /api/something/somethingElse</api>
                    <Accept>application/xml</Accept>
                    <ContentType>application/xml</ContentType>
                    <Parameters>id</Parameters>
                    <Method>POST</Method>
                    <ReceiveFile>true</ReceiveFile>
                    <Category>Cat3</Category>
                </Call>
                <Call>
                    <Description>blah blah 3</Description>
                    <api>POST /api/something/somethingElse/etc</api>
                    <Accept>application/xml</Accept>
                    <ContentType>application/xml</ContentType>
                    <Parameters>id</Parameters>
                    <Method>POST</Method>
                    <ReceiveFile>true</ReceiveFile>
                    <Category>Cat2</Category>
                </Call>
            </Calls>
        </x:XData>
    </XmlDataProvider>
</Window.Resources>

Later bind it to your list. Like this:

<DockPanel LastChildFill="True" 
           DataContext="{Binding Source={StaticResource CallsData}, XPath=/Calls/Call}">
    <StackPanel DockPanel.Dock="Bottom">
        <TextBlock Text="{Binding XPath=Description, StringFormat='Description = {0}'}"/>
        <TextBlock Text="{Binding XPath=api, StringFormat='api = {0}'}"/>
        <TextBlock Text="{Binding XPath=Accept, StringFormat='Accept = {0}'}"/>
        <TextBlock Text="{Binding XPath=ContentType, StringFormat='ContentType = {0}'}"/>
        <TextBlock Text="{Binding XPath=Parameters, StringFormat='Parameters = {0}'}"/>
        <TextBlock Text="{Binding XPath=Method, StringFormat='Method = {0}'}"/>
        <TextBlock Text="{Binding XPath=ReceiveFile, StringFormat='ReceiveFile = {0}'}"/>
        <TextBlock Text="{Binding XPath=Category, StringFormat='Category = {0}'}"/>
    </StackPanel>
    <ListBox Name="list" 
            IsSynchronizedWithCurrentItem="True"
            ItemsSource="{Binding}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding XPath=Category}"/>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
</DockPanel>