I am trying to pound data binding and wpf into my (apparently thick) head. I have a very simple program where I have an ObservableCollection of objects that contain a single property -- a string. When I run it, it presents the listbox as one character per line, and only of the first item.
This must be simple, but I'm stumped. If I remove the 'binding.Path = ...", then it will display "expOne.FNode" on each line (thrice).
Thank you!
Code below:
MainWindow.xaml.cs:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace expOne
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
ObservableCollection<FNode> MyFileList = new ObservableCollection<FNode>();
public MainWindow()
{
MyFileList.Add ( new FNode ( "alpha" ) );
MyFileList.Add ( new FNode ( "bravo" ) );
MyFileList.Add ( new FNode ( "charlie" ) );
InitializeComponent();
Binding binding = new Binding();
binding.Source = MyFileList;
binding.Path = new PropertyPath ( "Name" );
mylistbox.SetBinding ( ListBox.ItemsSourceProperty, binding );
}
}
}
FNode.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace expOne
{
public class FNode
{
private string m_name;
public string Name
{
get { return ( m_name ); }
set { m_name = value; }
}
public FNode ( string n )
{
m_name = n;
}
public FNode()
{
m_name = "Bob";
}
}
}
MainWindow.xaml:
<Window x:Class="expOne.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListBox Name="mylistbox" HorizontalAlignment="Stretch" FontSize="15" >
<!--<ListBoxItem Content="First or Last" />-->
</ListBox>
</Grid>
</Window>
That's because you bound the 'Name' property to the ItemsSource of you Listbox, essentially telling the listbox to bind to the characters in the string. The listbox binds to the combination of the Source and Path properties.
To bind the Listbox to the list, just set the source. To display the Name, set the ListBox's DisplayMemberPath property to "Name"
Actually, it's much easier to bind declaratively. You can set the list as the DataContext of the Listbox eg.
ObservableCollection<FNode> MyFileList = new ObservableCollection<FNode>();
public MainWindow()
{
MyFileList.Add ( new FNode ( "alpha" ) );
MyFileList.Add ( new FNode ( "bravo" ) );
MyFileList.Add ( new FNode ( "charlie" ) );
InitializeComponent();
mylistbox.DataContext=MyFileList;
}
and then set the binding in XAML:
<ListBox Name="mylistbox" ItemsSource="{Binding}" DisplayMemberPath="Name" />
Data binding allows to you to completely separate the data from your window. In fact, the MVVM style prescribes that your presentation data (the ViewModel) should be a separate class from your window (your View). This way you can bind different data classes to the same view, or different views to the same object.
Assuming you created a class named MyProjectViewModel with Name and Files properties, you could write this:
public MyProjectViewModel TheProject {get;set;}
public MainWindow()
{
TheProject=new MyProject();
InitializeComponent();
this.DataContext=TheProject;
}
and then bind any elements you need to this ViewModel's properties, eg:
<TextBox Name="projectName" Text="{Binding Name}" />
<ListBox Name="projectFiles" ItemsSource="{Binding Files}"
DisplayMemberPath="Name" />
No need to set path on binding, just set Source for your binding. Remove this:
binding.Path = new PropertyPath ( "Name" );
OR
Simply omit the binding and directly set ItemsSource on listBox:
MyFileList.Add(new FNode("alpha"));
MyFileList.Add(new FNode("bravo"));
MyFileList.Add(new FNode("charlie"));
InitializeComponent();
mylistbox.ItemsSource = MyFileList;
and set DisplayMemberPath on ListBox to Name otherwise it will call ToString() on your class FNode and will print fully qualified name of your class that's why you seeing expOne.FNode to be print thrice.
<ListBox Name="mylistbox" HorizontalAlignment="Stretch"
FontSize="15" DisplayMemberPath="Name"/>
Related
In MODEL, there is a list<string>.
In VIEWMODEL, there is IList<MODEL>.
And I want to binding at ComboBox in VIEW
But when i binding ItemsSource to MODEL list, VIEW look to VIEWMODEL.
So binding alway fail.
How can solve this problem?
class Model
{
private IList<string> _comboItems;
public IList<string> ComboItems
{
get => _comboItems;
set => SetProperty(ref _comboItems, value);
}
}
class ViewModel
{
private IList<Model> _modelList;
public IList<Model> ModelList
{
get => _modelList;
set => SetProperty(ref _modelList, value);
}
}
VIEW
<....>
<GridView>
<GridViewComboBoxColumn ItemsSource="{binding ModelList}"/>
</GridView>
I guess that you have a datagrid and you want to populate a column by combobox with strings come from ComboItems. If this is your goal: you have to keep in mind the following points.
you have to set the DataContext pointing your
Your ViewModel has, at least implements INotifyPropertyChanged, however if you have to do a large use of mvvm I suggest you to use one mvvm framework like MVVM Light or Caliburn Micro there are many however
the ItemsSource of your datagrid have to be able to notify changes to your view, it has to be something like an ObservableCollection or a BindingList
If you want to change the comboboxitems at runtime add or remove or items also ComboBoxItems has to be a BindingList ... it is not very good to have bindinglist in a model.
well based on what I say you could do in this way I use traditional WPF your code seems to be UWP
MainWindow.xaml:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx /2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded">
<Grid>
<DataGrid ItemsSource="{Binding ModelList}">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" />
<DataGridTemplateColumn Header="MyComboColumn" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource ="{Binding ComboItems}" BorderThickness="0" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
namespace WpfApplication2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
ViewModel vm = new ViewModel();
DataContext = vm;
vm.ModelList = new BindingList<Model>();
Model md = new Model();
md.ComboItems = new List<string>();
md.ComboItems.Add("string1");
md.ComboItems.Add("string2");
md.ComboItems.Add("string3");
vm.ModelList.Add(md);
md = new Model();
md.ComboItems = new List<string>();
md.ComboItems.Add("string4");
md.ComboItems.Add("string5");
md.ComboItems.Add("string6");
vm.ModelList.Add(md);
}
}
}
ViewModel
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
namespace WpfApplication2
{
public class ViewModel : INotifyPropertyChanged
{
private BindingList<Model> _modelList;
public BindingList<Model> ModelList
{
get { return _modelList; }
set { _modelList = value;
NotifyPropertyChanged("ModelList");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
}
Model
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace WpfApplication2
{
public class Model
{
private IList<string> _comboItems;
public IList<string> ComboItems
{
get { return _comboItems; }
set { _comboItems = value; }
}
}
}
i'm trying to learn WPF and on top of that the MVVM style of doing things.
I have a simple practice app in which i would like to display codes in a combo box.
My Code
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace SteamCodes
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private ObservableCollection<Codes> codes;
public MainWindow()
{
InitializeComponent();
codes = new ObservableCollection<Codes>()
{
new Codes() {CodeID = "1", Code="CODETEXT"}
};
steamCode.ItemsSource = codes.ToString();
}
}
public class Codes
{
public string CodeID { get; set; }
public string Code { get; set; }
}
}
My XAML
<Window x:Class="SteamCodes.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:SteamCodes"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ComboBox x:Name="steamCode" ItemsSource="{Binding Source = Codes}" HorizontalAlignment="Left" Height="43" Margin="122,37,0,0" VerticalAlignment="Top" Width="259"/>
</Grid>
</Window>
At the moment my Combo box is Pulling through as each option in the ComboBox is a letter from the line 'System.Collections.Objectmodel.ObservableCollection`1[SteamCodes.Codes]'
Everyone of those letters is a different drop down option in the combo box.
Any Ideas where i have gone wrong.
Your ComboBox ItemSource must be a collection of items, not a string:
steamCode.ItemsSource = codes;
You also have to specify which property of your item must be considered as value to be shown in combobox by setting DisplayMemberPath property:
steamCode.DisplayMemberPath = "Code";
To specify which property of bound objects will be used as actual selected value you have to use SelectedValuePath property:
steamCode.SelectedValuePath = "CodeID";
The MVVM approach is this:
public class ViewModel
{
public ObservableCollection<Codes> Codes { get; }
= new ObservableCollection<Codes>();
}
The MainWindow constructor:
public MainWindow()
{
InitializeComponent();
var viewModel = new ViewModel();
viewModel.Codes.Add(new Codes { CodeID = "1", Code = "CODETEXT" });
DataContext = viewModel;
}
The XAML:
<ComboBox ItemsSource="{Binding Codes}" DisplayMemberPath="Code" .../>
I have a simple wpf app, that I want to try to use ItemsSource binding from xaml. When I click the button. it should be updated also in the UI, but it doesn't.
Why doesn't it work?
Xaml code:
<Window x:Class="SendRawEthernetPacketsGUI.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<ComboBox HorizontalAlignment="Left" Margin="76,65,0,0" VerticalAlignment="Top" Width="120" ItemsSource="{Binding test}"/>
<Button Content="Button" HorizontalAlignment="Left" Margin="90,171,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
</Grid>
C# code:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace SendRawEthernetPacketsGUI
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public ObservableCollection<string> test = new ObservableCollection<string>();
public Window1()
{
InitializeComponent();
DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
test.Add("fdfddf");
}
}
}
It feels kinda stupid to even ask this, but even looking in google didn't help me. So can you?
Your binding never worked in the first place. You can only bind to properties not fields.
Try this instead:
public ObservableCollection<string> test { get; set; }
public Window1()
{
Test = new ObservableCollection<string>();
}
Or if you want some tricky C# 6 magic:
public ObservableCollection<string> test => new ObservableCollection<string>();
This is a function bodied-member and compiles to a read-only property that is initialized to the new ObservableCollection
Caveats/Design Errors:
Note that in both cases you aren't using INotifyPropertyChanged, so wholesale assignments to the collection won't be picked up by the UI. You should also be using PascalCase for your public properties, and using a proper view model instead of binding to the code-behind.
I've got a weird error when trying to create a simple sample using the latest version of Reactive UI.
The window opens and I get a system error
Couldn't find view for 'Hi Bob!'
note: 'Hi Bob!' is the first item in the list.
What am I missing here?
Thanks.
versions
ReactiveUI 6.5.0
Splat 1.6.2
.net 4.5
Sample code
xaml
<Window x:Class="ListBind.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel Orientation="Horizontal">
<ListBox Name="ListBox1"></ListBox>
</StackPanel>
</Grid>
Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using ReactiveUI;
namespace ListBind
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, IViewFor<ViewModel>
{
public MainWindow()
{
ViewModel = new ViewModel();
DataContext = ViewModel;
InitializeComponent();
this.OneWayBind(ViewModel, m => m.Items, v => v.ListBox1.ItemsSource);
}
public ViewModel ViewModel
{
get { return (ViewModel)GetValue(ViewModelProperty); }
set { SetValue(ViewModelProperty, value); }
}
public static readonly DependencyProperty ViewModelProperty =
DependencyProperty.Register("ViewModel", typeof(ViewModel), typeof(MainWindow), new PropertyMetadata(null));
object IViewFor.ViewModel
{
get { return ViewModel; }
set { ViewModel = (ViewModel)value; }
}
}
public class ViewModel : ReactiveObject
{
public ReactiveList<string> Items = new ReactiveList<string>(new[] { "Hi Bob!", "Two", "Three" });
}
}
The thing with ReactiveUI when you bind to things like a ListBox using the OneWayBind method, is that it will try to automatically apply a custom template for the data based upon the views it finds with Splat.Locator.Resolve. In your case, it is trying to find and build a view based on the "Hi Bob!" ViewModel, which obviously doesn't exist.
What you should do is force it to use a custom data template so that it doesn't try to apply a non-existing template. With a template below, it shouldn't try and resolve a view for you, but rather stick the "Hi Bob!" value into the TextBlock.
<ListBox x:Name="ListBox1">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
There is a slim chance that ReactiveUI will still ignore that (I cannot verify right now), so if that is the case, replace the OneWayBind binding with the traditional ItemSource={Binding Data}.
I've just started working with WPF, and to start with, I'd like to know how to programatically add instances of my own custom class with the one property of 'Name' to a listbox, and the listbox will show each element as its name in the UI, rather than as "MyNamespace.CustomClass".
I've read vague things about DataContexts and DataBinding and DataTemplates, but I want to know the absolute minimum I can do, preferably with as little XAML as possible - I find it fairly bewildering.
Thanks!
I know you want to avoid binding but I'll throw this out there anyway. try not to be too scared of XAML, it's a bit crazy to start with but once you get used to all of the {binding}s it's actually pretty obvious, a simple example of binding a listbox to a collection in code behind would go something like this.
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="350" Width="525">
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Window>
The DataContext property in Window tells it where bindings will look by default (which in this case is the window) and the data template tells the list box how to display each item found in the collection.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApplication1
{
public class MyClass
{
public string Name { get; set; }
}
public partial class MainWindow : Window
{
public ObservableCollection<MyClass> Items
{
get { return (ObservableCollection<MyClass>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register("Items", typeof(ObservableCollection<MyClass>), typeof(MainWindow), new PropertyMetadata(null));
public MainWindow()
{
InitializeComponent();
Items = new ObservableCollection<MyClass>();
Items.Add(new MyClass() { Name = "Item1" });
Items.Add(new MyClass() { Name = "Item2" });
Items.Add(new MyClass() { Name = "Item3" });
Items.Add(new MyClass() { Name = "Item4" });
Items.Add(new MyClass() { Name = "Item5" });
}
}
}
When pasted in to Visual Studio as is the above code should show this.