Guillermo
Guillermo

Reputation: 82

Stuck with simple data binding in XAML

I have list of strings that I want to use in several ComboBox controls in different tabs of a TabControl. The list of strings is in a public ObservableCollection. The problem is, that I can't get this collection to show in the <Window.Resources> section of the XAML file. Here is the code in a clean new application:

using System;
using System.Collections.ObjectModel;
using System.Windows;

namespace APX_Interface
{
    public partial class MainWindow : Window
    {
         public MainWindow()
        {
            InitializeComponent();
        }       
        
        public ObservableCollection<String> MyStringList; // not initialized yet

        public class NameList : ObservableCollection<String>
        {
            public NameList() : base()
            {
                Add("Willam");
                Add("Isak");
                Add("Victor");
                Add("Jules");
            }
        }
    }
}

Here is the XAML:

Window x:Class="APX_Interface.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:APX_Interface"
    mc:Ignorable="d"
    Title="MainWindow" Height="450" Width="800">

<Window.Resources>
    <local:MyStringList x:Key="Strings"/>
    <local:NameList x:Key="Names"/>
</Window.Resources>
<Grid>
    
</Grid>

The only thing that shows up for autocomplete when I type local: is App. Compiling the code I get several errors like this:

Error The tag 'MyStringList' does not exist in XML namespace 'clr-namespace:APX_Interface'.

Error XDG0008 The name "MyStringList" does not exist in the namespace "clr-namespace:APX_Interface".

Error XLS0414 The type 'local:MyStringList' was not found. Verify that you are not missing an assembly reference and that all referenced assemblies have been built. The same errors for the class NameList

After looking at many examples, and other discussions here I can't pinpoint what I'm missing.

Thanks for all the suggestions, unfortunately we haven't resolved this issue yet. I'm posting here the code with the changes suggested and my comments:

using System;
using System.Collections.ObjectModel;
using System.Windows;

namespace APX_Interface
{
    public  class NameList : ObservableCollection<String>
    // This class is now THE ONLY thing visible in <Window.Resources> 
    // but not any instance of the class.
    {
        public NameList() : base()
        {
        }
    }
      
    public partial class MainWindow : Window
    {
        // This instance of the class is NOT visible in <Window.Resources> ???
        public NameList MyNames { get { return _names;} }

        // nor is this
        public ObservableCollection<String> MyNumbers { get { return _numbers; } }

        // nope
        public NameList _names = null;

        //Neither this one
        public ObservableCollection<String> _numbers = null;

        public MainWindow()
        {
            InitializeComponent();

            // this doesn't make any difference
            DataContext = this;

            _names = new NameList(); 
            _names.Add("fellow");           // populate dynamically

            var _nr = new string[]     // static strings
            {
                "One",
                "Two",
                "etc.",
            };

            _numbers = new ObservableCollection<string>(_nr);
        }
    }
}

Upvotes: 0

Views: 337

Answers (4)

EldHasp
EldHasp

Reputation: 7908

A few examples of accessing the collection in XAML and from Code Behind.

First example: A collection in a static property.

XAML:

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <ListBox ItemsSource="{x:Static local:MyValues.NameList}" />
    <StackPanel Grid.Column="1">
        <TextBox x:Name="tbNew"/>
        <Button Content="Add" Click="Button_Click"/>
    </StackPanel>
</Grid>

Code Behind:

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        MyValues.NameList.Add(tbNew.Text);
    }

Second example: a collection instance is created in the XAML resources.

XAML:

<Window.Resources>
    <local:StringCollectionINCC x:Key="Strings">
        <sys:String>Willam</sys:String>
        <sys:String>Isak</sys:String>
        <sys:String>Victor</sys:String>
        <sys:String>Jules</sys:String>
    </local:StringCollectionINCC>
</Window.Resources>
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <ListBox ItemsSource="{Binding Mode=OneWay, Source={StaticResource Strings}}" />
    <StackPanel Grid.Column="1">
        <TextBox x:Name="tbNew"/>
        <Button Content="Add" Click="Button_Click"/>
    </StackPanel>
</Grid>

Code Behind:

    private void Button_Click(object sender, RoutedEventArgs e)
    {

        StringCollectionINCC list = (StringCollectionINCC)Resources["Strings"];

        list.Add(tbNew.Text);
    }

Third example (best): creating collections in the MVVM pattern.

To create a team, an additional class is used that implements ICommand:

/// <summary>Executing Delegate.</summary>
/// <param name="parameter">Command parameter.</param>
public delegate void ExecuteHandler(object parameter);
/// <summary>CanExecuting Delegate.</summary>
/// <param name="parameter">Command parameter.</param>
/// <returns><see langword="true"/> - if command execution is allowed.</returns>
public delegate bool CanExecuteHandler(object parameter);

/// <summary>A class implementing the ICommand interface for creating WPF commands.</summary>
public class RelayCommand : ICommand
{
    private readonly CanExecuteHandler _canExecute = CanExecuteDefault;
    private readonly ExecuteHandler _execute;
    private readonly EventHandler _requerySuggested;

    public event EventHandler CanExecuteChanged;

    /// <summary>The constructor of the command.</summary>
    /// <param name="execute">Command Executable Method.</param>
    /// <param name="canExecute">Team Status Method.</param>
    public RelayCommand(ExecuteHandler execute, CanExecuteHandler canExecute = null)
    {
        _execute = execute ?? throw new ArgumentNullException(nameof(execute));

        _canExecute = canExecute;

        _requerySuggested = (o, e) => Invalidate();
        CommandManager.RequerySuggested += _requerySuggested;
    }

    /// <summary>The method of invoking an event about a change in command status.</summary>
    public void Invalidate() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);

    public bool CanExecute(object parameter) => _canExecute == null ? true : _canExecute.Invoke(parameter);

    public void Execute(object parameter) => _execute?.Invoke(parameter);

    /// <summary>Default CanExecute Method/</summary>
    /// <param name="parameter">>Command parameter.</param>
    /// <returns>Is always <see langword="true"/>.</returns>
    public static bool CanExecuteDefault(object parameter) => true;
}

ViewModel with collection and command:

/// <summary>ViewModel</summary>
public class MainVM
{
    public ObservableCollection<string> NameList { get; } 
        = new ObservableCollection<string>()
        {
            "Willam",
            "Isak",
            "Victor",
            "Jules"

        };

    private RelayCommand _addCommand;
    public RelayCommand AddCommand => _addCommand 
        ?? (_addCommand = new RelayCommand(AddMethod, AddCanMethod));

    /// <summary>A method that checks that a parameter can be cast to
    /// a string and that string is not empty.</summary>
    /// <param name="parameter">Command parameter.</param>
    /// <returns><see langword="true"/> - if the conditions are met.</returns>
    private bool AddCanMethod(object parameter)
        => parameter is string val
        && !string.IsNullOrWhiteSpace(val);

    /// <summary>Method to add a value to a collection.</summary>
    /// <param name="parameter">Valid command parameter.</param>
    private void AddMethod(object parameter)
        => NameList.Add((string) parameter);
}

Locator - A commonly used solution for accessing all ViewModel or other data containers:

/// <summary>Contains all ViewModel. In this case, only one MainVM.</summary>
public class Locator 
{
    public MainVM MainVM { get; } = new MainVM();
}

XAML App - here it is more convenient to create resources that may be needed in different Windows:

<Application.Resources>
    <local:Locator x:Key="Locator"/>
</Application.Resources>

XAML Windows: setting ViewModel in a DataContext and binding properties of elements.

<Window ....
        DataContext="{Binding MainVM, Mode=OneWay, Source={StaticResource Locator}}">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <ListBox ItemsSource="{Binding NameList}" />
        <StackPanel Grid.Column="1">
            <TextBox x:Name="tbNew"/>
            <Button Content="Add"
                    Command="{Binding AddCommand, Mode=OneWay}"
                    CommandParameter="{Binding Text, ElementName=tbNew}"/>
        </StackPanel>
    </Grid>
</Window>

Upvotes: 1

Clemens
Clemens

Reputation: 128013

You have declared NameList as a nested class inside the MainWindow class. Move it out of there.

Besides that, you don't need to declare a MyStringList field in MainWindow, and NameList doesn't need to be an ObservableCollection, since you apparently never add or remove elements to/from the collection after it was initialized.

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }       
}

public class NameList : List<String>
{
    public NameList()
    {
        Add("Willam");
        Add("Isak");
        Add("Victor");
        Add("Jules");
    }
}

Now use it like this:

<Window.Resources>
    <local:NameList x:Key="Names"/>
</Window.Resources>
...
<ComboBox ItemsSource="{StaticResource Names}" .../>

Instead of declaring a NameList class in code, you may as well directly declare a string list resource in XAML.

Add XAML namespace declarations like these:

xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:coll="clr-namespace:System.Collections.Specialized;assembly=System"

and this resource:

<coll:StringCollection x:Key="Names">
    <sys:String>Willam</sys:String>
    <sys:String>Isak</sys:String>
    <sys:String>Victor</sys:String>
    <sys:String>Jules</sys:String>
</coll:StringCollection>

EDIT: In case you want to be able to manipulate the string collection in code behind, declaring a string list resource is the wrong apprach.

Instead, declare a view model class with a NameList property

public class ViewModel : INotifyPropertyChanged
{
    public ObservableCollection<string> NameList { get; }
        = new ObservableCollection<string>();
}

and assign it to the DataContext of the MainWindow

private readonly ViewModel viewModel = new ViewModel();

public MainWindow()
{
    InitializeComponent();

    DataContext = viewModel;

    viewModel.NameList.Add("Willam");
    viewModel.NameList.Add("Isak");
    viewModel.NameList.Add("Victor");
    viewModel.NameList.Add("Jules");
}

In XAML, bind the ComboBox like

<ComboBox ItemsSource="{Binding NameList}" .../>

EDIt2: Another, very simple approach may be a static member, e.g. in the MainWindow class, like

public partial class MainWindow : Window
{
    ...

    public static List<string> NameList { get; } = new List<string>
    {
        "Willam", "Isak", "Victor", "Jules"
    };
}

which would be used like this:

<ComboBox ItemsSource="{x:Static local:MainWindow.NameList}" .../>

Upvotes: 0

EldHasp
EldHasp

Reputation: 7908

There are several solutions, but to choose a specific one, you need to give more details of the task.

If you need a simple collection of strings, then you don’t need to create your own class. Better to use, as @Clements wrote, StringCollection.

If you need the ability to change the collection (INotifyCollectionChanged interface), you can add your own class. But you need to build it in the namespace, and not embed it in another class. Initializing it with values ​​is better in XAML resources, rather than in C#-code.

namespace APX_Interface
{
    public class StringCollectionINCC : ObservableCollection<string> { }
}

If there is a need to create a global instance accessible from anywhere in the application, then you can make a static property that provides this instance.

namespace APX_Interface
{
    public  static class MyValues
    {
        public static StringCollectionINCC NameList { get; }
            = new StringCollectionINCC()
            {
                "Willam",
                "Isak",
                "Victor",
                "Jules"
            };
    }
}

You can get it in XAML like this:

<Window.Resources>
    <local:StringCollectionINCC x:Key="Strings">
        <sys:String>Willam</sys:String>
        <sys:String>Isak</sys:String>
        <sys:String>Victor</sys:String>
        <sys:String>Jules</sys:String>
    </local:StringCollectionINCC>
    <x:Static Member="local:MyValues.NameList" x:Key="Names"/>
</Window.Resources>
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <ListBox ItemsSource="{Binding Mode=OneWay, Source={StaticResource Strings}}" />
    <ListBox Grid.Column="1" ItemsSource="{Binding Mode=OneWay, Source={StaticResource Names}}" />
</Grid>

You can also immediately, without resources, set a link to a static instance:

    <ListBox Grid.Column="1" ItemsSource="{x:Static local:MyValues.NameList}" />

Upvotes: 0

Rashedul.Rubel
Rashedul.Rubel

Reputation: 3584

If you need to use local:NameList then you have to do as the answer above. And if you need to use MyStringList property, then you have to do like below:

 public partial class MainWindow : Window
        {
            public ObservableCollection<string> _myStringList=new ObservableCollection<string>(); // not initialized yet
            public ObservableCollection<string> MyStringList
            {
                get
                {
                    return _myStringList;
                }
                set
                {
                    _myStringList = value;
                }
            }
    

        public MainWindow()
        {
            
            InitializeComponent();
            fillCombo();
            DataContext = this;
        }
        public void fillCombo()

        {

            MyStringList.Add("Willam");
            MyStringList.Add("Isak");
            MyStringList.Add("Victor");
            MyStringList.Add("Jules");
        }
    }

in XML:

<ComboBox ItemsSource="{Binding MyStringList}" Name="comboBox1"   Margin="34,56,0,0"
              VerticalAlignment="Top"
              Width="200"/>

Note: you can also create a usercontrol or customcontrol in your project and then design the control with your desired element. After that you can use created control whichever XML pages you want.

Upvotes: 0

Related Questions