0
votes

I have a ListView that I would like to populate in XAML. I'm using a custom DataTemplate to make each ListViewItem added contain a Label and a TextBlock.

The problem is I need to dynamically populate the text of the TextBlock of each ListViewItem with data from a settings property, and I don't know how to create this binding.

Right now I am populating the ListView with an XmlDataProvider, but I can't (or at least can't figure out how to) bind values to the xml data. (I'm not stuck using this method of data population, it's just what I was originally doing when I ran into this problem.)

Basically I need something as follows: The user enters some data into a text box. That data is saved to user settings. When that happens, the corresponding TextBlock of the ListViewItem in the ListView is updated with the user setting data.

Normally I would bind a TextBlock's text to a user setting as follows:

Text="{Binding Source={x:Static properties:Settings.Default},Path=User_Data_1}"

But how do I do this when the text of the TextBlock is defined in the DataTemplate?

My DataTemplate and XmlDataProvider:

    <DataTemplate x:Key="listViewTemplate">
        <StackPanel Orientation="Vertical">
            <Label x:Name="lblName" Content="{Binding XPath=name}"/>
            <TextBlock x:Name="tbValue" Text="{Binding XPath=value}"/>
        </StackPanel>
    </DataTemplate>

    <XmlDataProvider x:Key="PagesData" XPath="Pages">
        <x:XData>
            <Pages xmlns="">
                <page id="page01">
                    <name>Text file:</name>
                    <value></value>
                    <source>Pages/Page_CreateFiles1.xaml</source>
                </page>
                <page id="page02">
                    <name>Xml file:</name>
                    <value></value>
                    <source>Pages/Page_CreateFiles2.xaml</source>
                </page>
                <page id="page03">
                    <name>Memory object database:</name>
                    <value></value>
                    <source>Pages/Page_CreateFiles3.xaml</source>
                </page>
                <page id="page04">
                    <name>Output database:</name>
                    <value></value>
                    <source>Pages/Page_CreateDB.xaml</source>
                </page>
            </Pages>
        </x:XData>
    </XmlDataProvider>

My ListView

<ListView x:Name="lvNavigation" 
          ItemTemplate="{DynamicResource listViewTemplate}"
          ItemsSource="{Binding Source={StaticResource PagesData}, XPath=page}"/>
2

2 Answers

1
votes

Create a view model with a collection of items

public class Item
{
    public string Name { get; set; }
    public string Value { get; set; }
}

public class ViewModel
{
    public ObservableCollection<Item> Items { get; } = new ObservableCollection<Item>();
}

and set the MainWindow's DataContext to an instance of the view model class

public MainWindow()
{
    InitializeComponent();

    var vm = new ViewModel();
    DataContext = vm;

    vm.Items.Add(new Item { Name = "Name 1", Value = "Value 1" });
    vm.Items.Add(new Item { Name = "Name 2", Value = "Value 2" });
}

Bind to it like this:

<ItemsControl ItemsSource="{Binding Items}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <StackPanel>
                <TextBlock Text="{Binding Name}"/>
                <TextBlock Text="{Binding Value}"/>
            </StackPanel>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>
0
votes

Clemmens answer is right, but I just wanted to put it out there that I basically did his approach with a slight modification. I used events to trigger the data change in the listview.

I think it is my own fault though, because I didn't explain my problem well enough. First of all, I wanted to do everything from xaml and I don't think that was possible. Second, I failed to mention that I was using pages in a frame, where the data was coming from the pages and the listview was in my main window that contained the frame. So that's why I ended up using events to communicate between the page and the main window.

So in my main window I've defined my observable collection:

    ObservableCollection<NavItem> NavItems = new ObservableCollection<NavItem>();

    public MainWindow()
    {
        InitializeComponent();

        NavItems.Add(new NavItem { Name = "Text file:", Value = "", Source = "Pages/Page_CreateFiles.xaml" });
        NavItems.Add(new NavItem { Name = "Xml file:", Value = "", Source = "Pages/Page_CreateFiles.xaml" });
        NavItems.Add(new NavItem { Name = "Memory object db:", Value = "", Source = "Pages/Page_CreateFiles.xaml" });
        NavItems.Add(new NavItem { Name = "Output database:", Value = "", Source = "Pages/Page_CreateDB.xaml" });

        lvNavigation.ItemsSource = NavItems; 

        ...

    }

"NavItem" is a class that is subscribed to INotifyPropertyChanged. Posting that code will just be a lot, so check out how to do that here: INotifyPropertyChanged

Then in each page I set up an event that I call with the data to send:

    public static event EventHandler<NavUpdateMessage> UpdateMessage;

    private void OnUpdateMessage(int id, string message)
    {
        NavUpdateMessage navUpdateMessage = new NavUpdateMessage();
        navUpdateMessage.Id = id;
        navUpdateMessage.Message = message;

        var e = UpdateMessage;

        if (e != null)
            e(this, navUpdateMessage);
    }

With the main window subscribed to that event:

    public MainWindow()
    {
        ...

        Pages.Page_CreateFiles.UpdateMessage += Pages_UpdateMessage;
        Pages.Page_CreateDB.UpdateMessage += Pages_UpdateMessage;
    }

    private void Pages_UpdateMessage(object sender, NavUpdateMessage e)
    {
        Dispatcher.BeginInvoke(new Action(() =>
        {
            NavItems[e.Id].Value = e.Message;
        }));
    }

I'm sure there's a better, more simple approach to this, but this is what I could figure out. And even though I'm sure no one will see this because this question definitely did not get any traction, please feel free to suggest a better solution so at least I can learn.