views:

115

answers:

1

I've got a problem with my little app here that is conceptually very simple. I have an XML file that essentially just maps strings to strings.


Long-winded explanation warning


A sample file would look something like this:

<Candies>
  <Sees>Box1</Sees>
  <Hersheys>Box2</Hersheys>
  <Godiva>Box3</Godiva>
</Candies>

Although I could use a different schema, like:

<Candies>
  <Candy>
    <Name>Sees</Name>
    <Location>Box1</Location>
  </Candy>
</Candies>

...I opted not to, since the former didn't have any forseeable adverse side effects.

In code behind, I load the contents of my XML file into an XDocument with LINQ. I also have a List variable defined, because this is what I'm databinding my GUI to. CandyLocation looks like this:

public class CandyLocation
{
  public string Brand { get; set; }
  public string Location { get; set; }
}

And my simple GUI is just this:

<Page
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Height="Auto" Width="Auto">

    <Page.Resources>
        <DataTemplate x:Key="CandyTemplate">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition></ColumnDefinition>
                    <ColumnDefinition></ColumnDefinition>
                    <ColumnDefinition></ColumnDefinition>
                </Grid.ColumnDefinitions>
                <TextBox Grid.Column="0" Text="{Binding Brand}" Margin="3"></TextBox>
                <ComboBox Grid.Column="1" SelectedValue="{Binding Location}" ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Page}}, Path=DataContext.LocationNames}" Text="{Binding Location, Mode=TwoWay}" Margin="3"></ComboBox>
            </Grid>
        </DataTemplate>
    </Page.Resources>

    <DockPanel>
        <Button DockPanel.Dock="Bottom" Margin="3" Command="{Binding SaveCandiesCommand}">Apply Changes</Button>
        <Button DockPanel.Dock="Bottom" Margin="3" Command="{Binding AddNewCandyCommand}">Add Candy</Button>
        <ListBox DockPanel.Dock="Top" ItemsSource="{Binding CandyLocations}" ItemTemplate="{StaticResource CandyTemplate}" />
    </DockPanel>
</Page>

So the overview is this:

The application loads and then uses LINQ to load the XML file. When the GUI is presented, it calls "GetCandyLocations", which traverses the XML data and populates the List object with the contents of the XML file. Upon initial loading of the XML, the GUI renders properly (i.e. the candy brands and their locations appear correctly), but that's where the success story ends.


If I start from a blank XML file and add a brand, I do so by adding a new XElement to my XDocument root. Then I call OnPropertyChanged( "CandyLocations") to make the GUI update. The initial value for Location is "", so it's up to the user to select a valid location from the combobox. The problem is, I can't figure out how to get their selection databound correctly, such that I can update the XElement value. Because of this, when I save the candy locations, everything ends up with a blank location value. In addition, anytime the user clicks Add Candy, all of the previously selected location comboboxes get blanked out.

In summary:

  1. How should I handle the selection change in the GUI? I am using MVVM for this application, so I have avoided using the ComboBox's SelectionChanged event.
  2. Is there a way to databind directly from the GUI to the XDocument? I haven't tried it yet, but it would be best to avoid having multiple sources of data (i.e. XDocument for serialization and List for GUI rendering). Perhaps I can have the getter return the result of a LINQ query and pair it with a value converter???
  3. How would you change my implementation if you were to write this application? I'm still learning MVVM and WPF, so any advice would be really great.

Thanks!

+1  A: 

On your ComboBox, it looks like you might be getting a conflict between the SelectedValue and Text properties. Text is usually only used with IsEditable="True". Try using just SelectedItem:

<ComboBox SelectedItem="{Binding Location}" ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Page}}, Path=DataContext.LocationNames}" ></ComboBox>

If you want to use the XDocument directly as your data source you can use this (assuming XDocument is exposed from the VM as AvailableLocations):

<ComboBox ItemsSource="{Binding Path=AvailableLocations.Root.Elements}" SelectedValue="{Binding Location}"
          SelectedValuePath="Value" DockPanel.Dock="Top" DisplayMemberPath="Value"/>

If you'd rather do something like display the company names, just change DisplayMemberPath to "Name".

Also try using an ObservableCollection instead of a List for CandyLocations so you can get automatic change notifications when items are added or removed.

John Bowen
Thanks, John. Once I check in my code, I'll have to play with your suggestions. I already moved from List to ObservableCollection because in the former case + INotifyPropertyChanged, I just couldn't get anything to update when I try to add the brand even if I call OnPropertyChanged. However, once I changed to ObservableCollection, everything worked perfectly the first time. I am currently only serializing to XML upon loading and committing, but will try your databinding suggestion to the Elements. Thanks!
Dave
Oh, and you are correct about my ComboBox conflict, but the reason why nothing was appearing was because the Location value was never settable in my GUI, given my issue with using XDocument for the databinding. Once I changed it to the current implementation, everything worked great. However, once I change back to XDocument for everything, this could be a problem. So I'll change it now for sure. :)
Dave