Populate ComboBox items and SelectedValue in a Com

2019-08-21 07:44发布

问题:

I am developing a Windows 10 Universal App and I have trouble getting items to populate in a ComboBox (both ComboBox items and SelectedValue) that is within a GridView DataTemplate. I don't seem to be able to find a way to do this.

Here are the details:

I have an object (UserData) that can contain multiple instances of another object (List<VisitedCity>). On the XAML page, the multiple instances of VisitedCity are shown on a vertical GridView.

Each instance of VisitedCity can contain the CityName and VisitedDate. On the XAML page, the CityName is to be shown on a ComboBox and the visited date on a Date on a DatePicker inside each GridViewItem.

What doesn't work:

The ComboBox showing the selected city name of each VisitedCity needs to have it's list of items on it loaded from a List<string> variable (so that the user can change it) and the ComboBox SelectedValue needs to be loaded from the curretUserData object's VisitedCities property (CityName property).

I an trying to do this using x:Bind on the XAML in order to minimize the code behind.

I have included all my code from the sample app I did to troubleshoot this and I have also indicated the line of code on the XAML that I am having issues with.

Please note, this is highly simplified part of a larger app with VisistedCities as an example so that the problem can be demonstrated better. Thank you for all your help and input!

Here is my class file:

namespace mySampleApp
{
    class UserData
    {
        public string FullName { get; set; }
        public List<VisitedCity> VisitedCities { get; set; }
    }

    class VisitedCity
    {
        public string CityName { get; set; }
        public DateTime VisitedDate { get; set; }

    }

    class CityNameManager
    {
        public static List<string> GetListOfCitiesFromServer()
        {
            List<string> listOfCities = new List<string>();

            //code will be here to get the latest list from the server. for now we will manually return a list

            listOfCities.Add("City1");
            listOfCities.Add("City2");
            listOfCities.Add("City3");
            listOfCities.Add("City4");
            listOfCities.Add("City5");

            return listOfCities;

        }
    }
}

Here is my XAML:

<Page
    x:Class="mySampleApp.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:mySampleApp"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

        <Grid.RowDefinitions>
            <RowDefinition  Height="Auto"/>
            <RowDefinition  Height="Auto"/>
            <RowDefinition  Height="Auto"/>
            <RowDefinition  Height="Auto"/>
            <RowDefinition  Height="Auto"/>
        </Grid.RowDefinitions>

        <TextBox x:Name="txtFirstName" Grid.Row="0" Header="Enter Your Name" Text="{x:Bind curretUserData.FullName}"/>

        <TextBlock x:Name="label1" Grid.Row="1" TextWrapping="WrapWholeWords" Text="Please select all cities you have visited during the last 5 years. For each city please enter the data visited. If you visited a city multiple times, please have multiple entries for each time indicating the date visited each time." />

        <TextBlock x:Name="label2" Grid.Row="2" TextWrapping="WrapWholeWords" Text="Press the + button to add rows as needed. " />

        <Button x:Name="myButton" Grid.Row="3" Content="+" Click="myButton_Click" />

        <GridView x:Name="myGridView" Grid.Row="4" HorizontalContentAlignment="Stretch" SelectionMode="None" ItemsSource="{x:Bind curretUserData.VisitedCities}">
            <GridView.ItemsPanel>
                <ItemsPanelTemplate>
                    <ItemsStackPanel Orientation="Vertical" />
                </ItemsPanelTemplate>
            </GridView.ItemsPanel>

            <GridView.ItemContainerStyle>
                <Style TargetType="GridViewItem">
                    <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
                    <Setter Property="HorizontalAlignment" Value="Stretch"/>
                </Style>
            </GridView.ItemContainerStyle>

            <GridView.ItemTemplate>
                <DataTemplate x:DataType="local:VisitedCity">
                    <Grid HorizontalAlignment="Stretch">

                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto" />
                            <ColumnDefinition Width="*" />
                        </Grid.ColumnDefinitions>

                        <!-- This is the line of code which has issues-->
                        <ComboBox Grid.Column="0" x:Name="myTemplateComboBox"  ItemsSource="{local:CityList ??? }" SelectedValue="{x:Bind CityName ??? }" HorizontalAlignment="Stretch"  />

                        <DatePicker Grid.Column="1" x:Name="myTemplateDatePicker" Date="{x:Bind VisitedDate }" />

                    </Grid>
                </DataTemplate>
            </GridView.ItemTemplate>
        </GridView>
    </Grid>
</Page>

Here is my code behind:

namespace mySampleApp
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage : Page
    {
        //variables to use in XAML x:Bind
        private UserData curretUserData = new UserData();
        private List<string> CityList = new List<string>();

        public MainPage()
        {
            //get latest list of cities from the server
            CityList = CityNameManager.GetListOfCitiesFromServer();

            //add sample data to show on the page for demo
            curretUserData.FullName = "Sample User";
            List<VisitedCity> sampleUserVisitedCities = new List<VisitedCity>();
            sampleUserVisitedCities.Add(new VisitedCity {CityName = "City1", VisitedDate=DateTime.Parse("2/2/2016") });
            sampleUserVisitedCities.Add(new VisitedCity {CityName = "City2", VisitedDate = DateTime.Parse("2/2/2015") });
            curretUserData.VisitedCities = sampleUserVisitedCities;

            this.InitializeComponent();
        }

        private void myButton_Click(object sender, RoutedEventArgs e)
        {
            //add a new row
            GridViewItem gridViewItem = new GridViewItem();
            gridViewItem.ContentTemplate = myGridView.ItemTemplate;
            myGridView.Items.Add(gridViewItem);
        }
    }
}

回答1:

You need to modify your binding expression to grab from the parent page. x:Bind will only use the local DataContext, which in this case is the templated item.

<ComboBox Grid.Column="0" x:Name="myTemplateComboBox"  ItemsSource="{Binding ElementName=MyPage, Path=CityList }" SelectedValue="{x:Bind CityName, Mode=TwoWay}" HorizontalAlignment="Stretch"  />

In this case, I make the CityList a public property and then named the Page so that we could grab it using ElementName.

You also need to enable TwoWay Binding so that you can get the values set back to your data model after the user makes a change.

If you use an Observable collection instead of a List, then you can add to your GridView rows by modifying your data model. I was hitting an exception otherwise.

I also swapped out the initial username for PlaceholderText so that the user doesn't have to erase the sample name in order to type theirs.

Full code

<Page
    x:Class="SOCityGridView.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:SOCityGridView"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    x:Name="MyPage"
    mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

        <Grid.RowDefinitions>
            <RowDefinition  Height="Auto"/>
            <RowDefinition  Height="Auto"/>
            <RowDefinition  Height="Auto"/>
            <RowDefinition  Height="Auto"/>
            <RowDefinition  Height="Auto"/>
        </Grid.RowDefinitions>

        <TextBox x:Name="txtFirstName" Grid.Row="0" Header="Enter Your Name" Text="{x:Bind currentUserData.FullName}" PlaceholderText="Sample User"/>

        <TextBlock x:Name="label1" Grid.Row="1" TextWrapping="WrapWholeWords" Text="Please select all cities you have visited during the last 5 years. For each city please enter the data visited. If you visited a city multiple times, please have multiple entries for each time indicating the date visited each time." />

        <TextBlock x:Name="label2" Grid.Row="2" TextWrapping="WrapWholeWords" Text="Press the + button to add rows as needed. " />

        <Button x:Name="myButton" Grid.Row="3" Content="+" Click="myButton_Click" />

        <GridView x:Name="myGridView" Grid.Row="4" HorizontalContentAlignment="Stretch" SelectionMode="None" ItemsSource="{x:Bind currentUserData.VisitedCities}">
            <GridView.ItemsPanel>
                <ItemsPanelTemplate>
                    <ItemsStackPanel Orientation="Vertical" />
                </ItemsPanelTemplate>
            </GridView.ItemsPanel>

            <GridView.ItemContainerStyle>
                <Style TargetType="GridViewItem">
                    <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
                    <Setter Property="HorizontalAlignment" Value="Stretch"/>
                </Style>
            </GridView.ItemContainerStyle>

            <GridView.ItemTemplate>
                <DataTemplate x:DataType="local:VisitedCity">
                    <Grid HorizontalAlignment="Stretch">

                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto" />
                            <ColumnDefinition Width="*" />
                        </Grid.ColumnDefinitions>

                        <!-- This is the line of code which has issues-->
                        <ComboBox Grid.Column="0" x:Name="myTemplateComboBox"  ItemsSource="{Binding ElementName=MyPage, Path=CityList }" SelectedValue="{x:Bind CityName, Mode=TwoWay}" HorizontalAlignment="Stretch"  />

                        <DatePicker Grid.Column="1" x:Name="myTemplateDatePicker" Date="{x:Bind VisitedDate, Mode=TwoWay}" />

                    </Grid>
                </DataTemplate>
            </GridView.ItemTemplate>
        </GridView>
    </Grid>
</Page>

And Code Behind:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409

namespace SOCityGridView
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage : Page
    {
        //variables to use in XAML x:Bind
        private UserData currentUserData = new UserData();
        public List<string> CityList { get; set; } = new List<string>();

        public MainPage()
        {
            //get latest list of cities from the server
            CityList = CityNameManager.GetListOfCitiesFromServer();

            //add sample data to show on the page for demo
            List<VisitedCity> sampleUserVisitedCities = new List<VisitedCity>();
            sampleUserVisitedCities.Add(new VisitedCity { CityName = "City1", VisitedDate = DateTime.Parse("2/2/2016") });
            sampleUserVisitedCities.Add(new VisitedCity { CityName = "City2", VisitedDate = DateTime.Parse("2/2/2015") });
            currentUserData.VisitedCities = new ObservableCollection<VisitedCity>(sampleUserVisitedCities);

            this.InitializeComponent();
        }

        private void myButton_Click(object sender, RoutedEventArgs e)
        {
            currentUserData.VisitedCities.Add(new VisitedCity { CityName = "City1", VisitedDate = DateTime.Now });
        }
    }

    public class UserData
    {
        public string FullName { get; set; }
        public ObservableCollection<VisitedCity> VisitedCities { get; set; }
    }

    public class VisitedCity
    {
        public string CityName { get; set; }
        public DateTimeOffset VisitedDate { get; set; }

    }

    public class CityNameManager
    {
        public static List<string> GetListOfCitiesFromServer()
        {
            List<string> listOfCities = new List<string>();

            //code will be here to get the latest list from the server. for now we will manually return a list

            listOfCities.Add("City1");
            listOfCities.Add("City2");
            listOfCities.Add("City3");
            listOfCities.Add("City4");
            listOfCities.Add("City5");

            return listOfCities;

        }
    }
}