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);
}
}
}
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>
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.
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}" />
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}" .../>
Related
I have a custom usercontrol that I'm using in a DataTemplate. In this usercontrol I have a text box. Previously when I was implementing this usercontrol I would have no problems but now that I've put it into a Datatemplate it seems that something has gone wrong.
Each time I type something into the text box the code runs normally but at the end of my triggers the usercontrols constructor gets called again and clears my textbox. (Hidden threads call it so I have no idea where to even start looking for where this unintuitive call originates from)
I'm trying to figure out what is causing this constructor to fire again. The other binds seem to work well and the correct information is being populated and displayed. It's just that constructor that is called again after everything resolves and clears the internal variables of the ui control.
Current Execution:
I type into the textbox. The triggers get my value of the text box filters the lists accordingly and than the constructor gets called and the textbox resets to default value of "".
Desired Execution:
I type into the textbox. The triggers get my value of the text box filters the lists accordingly.
<UserControl x:Class="Analytics_Module.Views.TenantProfileFilterFieldsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Analytics_Module.Views"
xmlns:vm="clr-namespace:Analytics_Module.ViewModels"
xmlns:uiComponents="clr-namespace:Analytics_Module.UI_Components"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<DockPanel>
<DockPanel.DataContext>
<vm:TenantProfileFilterFieldsViewModel x:Name="test"/>
</DockPanel.DataContext>
....
<ScrollViewer HorizontalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding FiltersState.GroupedTenantNames, Mode=TwoWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<uiComponents:neoCombobox
LabelText="Tenant Names"
ListBoxItems="{Binding StaticLists.TenantNames, ElementName=test}"
DisplayListBoxItems ="{Binding}"
/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
CUSTOM USER CONTROL
<UserControl x:Class="Analytics_Module.UI_Components.neoCombobox"
x:Name="parent"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:model="clr-namespace:Analytics_Module.Models"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel DataContext="{Binding ElementName=parent}" Width="200">
<Label Name="ComboboxLabel"
Content="{Binding Path=LabelText, FallbackValue='Error'}" Margin="5"/>
<TextBox Name="InputField"
Text="{Binding Path=TextBoxValue, Mode=TwoWay, FallbackValue='Error', UpdateSourceTrigger='PropertyChanged'}"/>
<!--TODO rename -->
<ListBox Name="Something"
ItemsSource="{Binding Path=DisplayListBoxItems, FallbackValue={}, Mode=TwoWay}" >
<ListBox.ItemTemplate >
<DataTemplate >
<StackPanel>
<CheckBox Margin="-1"
Content="{Binding Name, FallbackValue='Error'}"
IsChecked="{Binding Check_Status, Mode=TwoWay, FallbackValue=true}">
</CheckBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</UserControl>
Back end of user contorl
using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Analytics_Module.Models;
using Analytics_Module.Utillity;
using System.Timers;
using System.Collections.Specialized;
namespace Analytics_Module.UI_Components
{
/// <summary>
/// Interaction logic for neoCombobox.xaml
/// </summary>
public partial class neoCombobox : UserControl
{
#region LabelText DP
public String LabelText
{
get { return (String)GetValue(LabelTextProperty); }
set { SetValue(LabelTextProperty, value); }
}
public static readonly DependencyProperty LabelTextProperty =
DependencyProperty.Register("LabelText",
typeof(string),
typeof(neoCombobox), new PropertyMetadata("")
);
#endregion
#region TextBoxValue DP
/// <summary>
/// Gets or sets the Value which is being displayed
/// </summary>
public String TextBoxValue
{
get { return (String)GetValue(TextBoxValueProperty); }
set { SetValue(TextBoxValueProperty, value); }
}
/// <summary>
/// Identified the TextBoxValue dependency property
/// </summary>
public static readonly DependencyProperty TextBoxValueProperty =
DependencyProperty.Register("TextBoxValue",
typeof(String),
typeof(neoCombobox),
new PropertyMetadata("")
);
#endregion
#region ListBoxItems DP
public ItemsChangeObservableCollection<MultiSelectDropDownListEntry> ListBoxItems
{
get { return (ItemsChangeObservableCollection<MultiSelectDropDownListEntry>)GetValue(ListBoxItemsProperty); }
set { SetValue(ListBoxItemsProperty, value); }
}
public static readonly DependencyProperty ListBoxItemsProperty =
DependencyProperty.Register("ListBoxItems",
typeof(ItemsChangeObservableCollection<MultiSelectDropDownListEntry>),
typeof(neoCombobox),
new PropertyMetadata(new ItemsChangeObservableCollection<MultiSelectDropDownListEntry>())
);
#endregion
#region DisplayListBoxItems DP
public ItemsChangeObservableCollection<MultiSelectDropDownListEntry> DisplayListBoxItems
{
get {
if (GetValue(DisplayListBoxItemsProperty) == null)
{
SetValue(DisplayListBoxItemsProperty, new ItemsChangeObservableCollection<MultiSelectDropDownListEntry>());
}
return (ItemsChangeObservableCollection<MultiSelectDropDownListEntry>)GetValue(DisplayListBoxItemsProperty);
}
set { SetValue(DisplayListBoxItemsProperty, value); }
}
public static readonly DependencyProperty DisplayListBoxItemsProperty =
DependencyProperty.Register("DisplayListBoxItems",
typeof(ItemsChangeObservableCollection<MultiSelectDropDownListEntry>),
typeof(neoCombobox),
new PropertyMetadata(new ItemsChangeObservableCollection<MultiSelectDropDownListEntry>())
);
#endregion
/// <summary>
/// _timer is used to determine if a user has stopped typing.
/// The timer is started when a user starts typing again or
/// types for the first time.
/// </summary>
private readonly Timer _timerKeyPress;
/// <summary>
/// _timer is used to determine if a user has left the typing.
/// The timer is started when a user starts typing again or
/// types for the first time.
/// </summary>
private readonly Timer _timerMouseLeave;
public neoCombobox()
{
if (TextBoxValue != "") return;
InitializeComponent();
_timerKeyPress = new Timer();
_timerKeyPress.Interval = 750;
_timerKeyPress.Elapsed += new ElapsedEventHandler(UserPausedTyping);
_timerKeyPress.AutoReset = false;
_timerMouseLeave = new Timer();
_timerMouseLeave.Interval = 550;
//_timerMouseLeave.Elapsed += new ElapsedEventHandler(UserLeft);
_timerMouseLeave.AutoReset = false;
}
//TODO Add property to determine if user preferes Mouse Leave of focus leave.
protected override void OnPreviewGotKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
Console.WriteLine("###############OnPreviewGotKeyboardFocus");
_timerMouseLeave.Stop();
base.OnPreviewGotKeyboardFocus(e);
}
protected override void OnPreviewLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
Console.WriteLine("------------OnPreviewLostKeyboardFocus");
_timerMouseLeave.Stop();
_timerMouseLeave.Start();
base.OnPreviewLostKeyboardFocus(e);
}
protected override void OnMouseEnter(MouseEventArgs e)
{
_timerMouseLeave.Stop();
base.OnMouseEnter(e);
}
protected override void OnMouseLeave(MouseEventArgs e)
{
_timerMouseLeave.Stop();
_timerMouseLeave.Start();
base.OnMouseLeave(e);
}
protected override void OnKeyUp(KeyEventArgs e)
{
_timerKeyPress.Stop();
_timerKeyPress.Start();
}
private void UserPausedTyping(object source, ElapsedEventArgs e)
{
this.Dispatcher.Invoke(() =>
{
Console.WriteLine("###############UserPausedTyping");
this.RefreshDisplayList();
});
}
private void UserLeft(object source, ElapsedEventArgs e)
{
this.Dispatcher.Invoke(() =>
{
Console.WriteLine("###############User Left");
this.TextBoxValue = "";
this.RefreshDisplayList();
});
}
protected void RefreshDisplayList()
{
int ItemsourceCount = 0;
foreach (MultiSelectDropDownListEntry entry in this.DisplayListBoxItems.ToList())
{
if (!entry.Check_Status) this.DisplayListBoxItems.Remove(entry);
}
if (this.TextBoxValue == "") return;
foreach (MultiSelectDropDownListEntry entry in this.ListBoxItems)
{
if (entry.Name.ToString().ToLower().Contains(this.TextBoxValue.ToLower()) && !this.DisplayListBoxItems.Contains(entry))
{
this.DisplayListBoxItems.Add(entry);
if (ItemsourceCount++ > 15) break;
}
}
}
}
}
You cannot always avoid the recreation of containers by the ItemsControl, but you can make your text data persistent by binding the Text property of your TextBox to a property of the view model and not to a property of the custom control itself.
Instead of writing:
<TextBox Name="InputField" Text="{Binding Path=TextBoxValue, Mode=TwoWay, FallbackValue='Error', UpdateSourceTrigger='PropertyChanged'}"/>
maybe write
<TextBox Name="InputField" Text="{Binding MyTextProperty, Mode=TwoWay, FallbackValue='Error', UpdateSourceTrigger='PropertyChanged'}"/>
and have the MyTextProperty defined in your view model like this:
public class GroupedTenantNameViewModel {
public string MyTextProperty { get; set; }
}
and make your FiltersState.GroupedTenantNames collection a collection of GroupedTenantNameViewModel items. This collection will be persistent even though the ItemsControl re-generates all items, and the binding will take care of putting data back in its place.
If you're not using the MVVM pattern at all, then I suggest you research a bit into it as it's made to deal with bindings the right way!
I'm new to MVVM and WPF and have been completely hung up on this issue for some time now. I'm trying to display a View (UserControl) based on the SelectedItem in a DataGrid. The UserControl renders with the data set in the constructor, but never updates.
I would really appreciate some insight from someone with experience in this. I tried adding a Mediator via setUpdateCallback and now the first row in the datagrid updates with the values of the other rows I click on, but this obviously isn't what I want, I need those updates to happen in the separate client view outside of the datagrid.
ClientPanel.xaml
<UserControl x:Class="B2BNet.View.ClientPanel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:VM="clr-namespace:B2BNet.ViewModel"
xmlns:V="clr-namespace:B2BNet.View"
xmlns:local="clr-namespace:B2BNet"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
mc:Ignorable="d">
<Grid>
<Grid.DataContext>
<VM:ClientPanel/>
</Grid.DataContext>
<TextBlock HorizontalAlignment="Left" Margin="10,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Height="43" Width="280" Text="{Binding Title}" FontSize="36" FontFamily="Global Monospace"/>
<DataGrid AutoGenerateColumns="False" x:Name="dataGrid" HorizontalAlignment="Left" Margin="10,60,0,10" VerticalAlignment="Top" ItemsSource="{Binding Clients}" SelectedItem="{Binding currentClient}">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding name}"></DataGridTextColumn>
<DataGridTextColumn Header="Active" Binding="{Binding active}"></DataGridTextColumn>
<DataGridTextColumn Header="Status" Binding="{Binding status}"></DataGridTextColumn>
</DataGrid.Columns>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding clientSelectionChanged_command}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
<V:Client HorizontalAlignment="Left" Margin="163,58,-140,0" VerticalAlignment="Top" Content="{Binding currentClient}" Height="97" Width="201"/>
</Grid>
Client.xaml
<UserControl x:Class="B2BNet.View.Client"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:VM="clr-namespace:B2BNet.ViewModel"
xmlns:local="clr-namespace:B2BNet"
mc:Ignorable="d">
<Grid>
<Grid.DataContext>
<VM:Client/>
</Grid.DataContext>
<Label x:Name="name" Content="Name:" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top"/>
<Label x:Name="active" Content="Active:" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="9,41,0,0"/>
<Label x:Name="status" Content="Status:" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="10,72,0,0"/>
<Label x:Name="namevalue" HorizontalAlignment="Left" Margin="59,10,0,0" VerticalAlignment="Top" Width="200" Height="26" Content="{Binding name}"/>
<Label x:Name="activevalue" HorizontalAlignment="Left" Margin="59,41,0,0" VerticalAlignment="Top" Width="200" Height="26" Content="{Binding active}"/>
<Label x:Name="statusvalue" HorizontalAlignment="Left" Margin="60,67,-1,0" VerticalAlignment="Top" Width="200" Height="26" Content="{Binding status}"/>
</Grid>
ViewModel - ClientPanel.cs
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows.Input;
using B2BNet.MVVM;
namespace B2BNet.ViewModel
{
public class ClientPanel : ObservableObject
{
public string Title { get; set; }
private Client _currentClient;
public Client currentClient
{
get
{
return _currentClient;
}
set
{
_currentClient = value;
RaisePropertyChangedEvent( "currentClient" );
Utility.print("currentClient Changed: " + _currentClient.name);
Mediator.Instance.Notify(ViewModelMessages.UpdateClientViews, currentClient);
}
}
private ObservableCollection<Client> _Clients;
public ObservableCollection<Client> Clients
{
get
{
return _Clients;
}
set
{
_Clients = value;
RaisePropertyChangedEvent("Clients");
Utility.print("Clients Changed");
}
}
public ClientPanel()
{
Title = "Clients";
Clients = new ObservableCollection<Client>();
for ( int i = 0; i < 10; i++ )
{
Clients.Add( new Client { name = "Client-" + i, status = "Current", active = true } );
}
currentClient = Clients.First();
currentClient.setUpdateCallback();
}
////////////
//COMMANDS//
////////////
public ICommand clientSelectionChanged_command
{
get { return new DelegateCommand( clientSelectionChanged ); }
}
private void clientSelectionChanged( object parameter )
{
B2BNet.Utility.print("clientSelectionChanged");
}
}
}
ViewModel - Client.cs
using System;
using B2BNet.MVVM;
namespace B2BNet.ViewModel
{
public class Client : ObservableObject
{
private String _name;
public String name
{
get
{
return _name;
}
set
{
_name = value;
RaisePropertyChangedEvent( "name" );
Utility.print("Client.name changed: " + value );
}
}
private Boolean _active;
public Boolean active
{
get
{
return _active;
}
set
{
_active = value;
RaisePropertyChangedEvent( "active" );
Utility.print("Client.active changed" + value );
}
}
private String _status;
public String status
{
get
{
return _status;
}
set
{
_status = value;
RaisePropertyChangedEvent( "status" );
Utility.print("Client.status changed" + value );
}
}
public Client()
{
name = "Set in Client Constuctor";
status = "Set in Client Constructor";
}
public void setUpdateCallback()
{
////////////
//Mediator//
////////////
//Register a callback with the Mediator for Client
Mediator.Instance.Register(
(Object o) =>
{
Client client = (Client)o;
name = client.name;
active = client.active;
status = client.status;
}, B2BNet.MVVM.ViewModelMessages.UpdateClientViews
);
}
}
}
Very Basic MVVM Framework
ObservableObject.cs
using System.ComponentModel;
namespace B2BNet.MVVM
{
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChangedEvent( string propertyName )
{
var handler = PropertyChanged;
if ( handler != null )
handler( this, new PropertyChangedEventArgs( propertyName ) );
}
}
}
Mediator.cs
using System;
using B2BNet.lib;
namespace B2BNet.MVVM
{
/// <summary>
/// Available cross ViewModel messages
/// </summary>
public enum ViewModelMessages { UpdateClientViews = 1,
DebugUpdated = 2
};
public sealed class Mediator
{
#region Data
static readonly Mediator instance = new Mediator();
private volatile object locker = new object();
MultiDictionary<ViewModelMessages, Action<Object>> internalList
= new MultiDictionary<ViewModelMessages, Action<Object>>();
#endregion
#region Ctor
//CTORs
static Mediator()
{
}
private Mediator()
{
}
#endregion
#region Public Properties
/// <summary>
/// The singleton instance
/// </summary>
public static Mediator Instance
{
get
{
return instance;
}
}
#endregion
#region Public Methods
/// <summary>
/// Registers a callback to a specific message
/// </summary>
/// <param name="callback">The callback to use
/// when the message it seen</param>
/// <param name="message">The message to
/// register to</param>
public void Register(Action<Object> callback,
ViewModelMessages message)
{
internalList.AddValue(message, callback);
}
/// <summary>
/// Notify all callbacks that are registed to the specific message
/// </summary>
/// <param name="message">The message for the notify by</param>
/// <param name="args">The arguments for the message</param>
public void Notify(ViewModelMessages message,
object args)
{
if (internalList.ContainsKey(message))
{
//forward the message to all listeners
foreach (Action<object> callback in
internalList[message])
callback(args);
}
}
#endregion
}
}
DelegateCommand.cs
using System;
using System.Windows.Input;
namespace B2BNet.MVVM
{
public class DelegateCommand : ICommand
{
private readonly Action<object> _action;
public DelegateCommand( Action<object> action )
{
_action = action;
}
public void Execute( object parameter )
{
_action( parameter );
}
public bool CanExecute( object parameter )
{
return true;
}
#pragma warning disable 67
public event EventHandler CanExecuteChanged;
#pragma warning restore 67
}
}
THE SOLUTION
The solution was a simple one. Thanks for the quick response Rachel :). I simple removed the following from each of the Views:
<!--<Grid.DataContext>
<VM:ClientPanel/>
</Grid.DataContext>-->
<!--<Grid.DataContext>
<VM:Client/>
</Grid.DataContext>-->
and it works perfectly, even allowing me to update the datagrid:
The problem is you are hardcoding the DataContext in your UserControls.
So your Client UserControl has it's DataContext hardcoded to a new instance of Client, and it is NOT the same instance that your ClientPanel UserControl is using. So the <V:Client Content="{Binding currentClient}" binding is pointing to a different instance of the property than the one the DataGrid is using.
So get rid of
<Grid.DataContext>
<VM:Client/>
</Grid.DataContext>
and change your Content binding to a DataContext one
<V:Client DataContext="{Binding currentClient}" .../>
and it should work fine.
I thought for sure I had some rant on SO about why you should NEVER hardcode the DataContext property of a UserControl, but the closest thing I can find right now is this. Hopefully it can help you out :)
Oh boy that's a lot of code and I'm not sure I understand everything you're trying to do. Here are a few important notes when binding with WPF.
Make sure the properties have public { get; set; }
If the value is going to be changing, make sure you add Mode=TwoWay, UpdateSourceTrigger=PropertyChanged to your bindings. This keeps your view and view model in sync and tells each other to update.
Use ObservableCollections and BindingLists for collections. Lists don't seem to work very well.
Make sure the DataContext matches the properties you are trying to bind to. If your DataContext doesn't have those properties, they aren't going to update.
A have read a lot of method about the ways of binding enum to combobox. So now in .Net 4.5 it should be pretty ease. But my code dont work.
Dont really understand why.
xaml:
<Window x:Class="SmartTrader.Windows.SyncOfflineDataWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="SyncOfflineDataWindow" Height="300" Width="300">
<Grid>
<StackPanel>
<ComboBox ItemsSource="{Binding StrategyTypes}" SelectedItem="{Binding StrategyType}" />
<Button Width="150" Margin="5" Padding="5" Click="Button_Click">Save</Button>
</StackPanel>
</Grid>
xaml.cs backend
namespace SmartTrader.Windows
{
/// <summary>
/// Interaction logic for SyncOfflineDataWindow.xaml
/// </summary>
public partial class SyncOfflineDataWindow : Window
{
public SyncOfflineDataWindow(IPosition position, ContractType type)
{
DataContext = new ObservablePosition(position);
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
}
}
}
View Model:
namespace SmartTrader.Entity
{
public class ObservablePosition : NotifyPropertyChanged, IPosition
{
public IEnumerable<StrategyType> StrategyTypes =
Enum.GetValues(typeof (StrategyType)).Cast<StrategyType>();
public ObservablePosition(IPosition position)
{
Strategy = position.Strategy;
}
private StrategyType _strategyType = StrategyType.None;
public StrategyType Strategy
{
get { return _strategyType; }
set
{
_strategyType = value;
OnPropertyChanged();
}
}
}
}
StrategyType is enum.
All i have got it is empty dropdown list
You are trying to bind to a private variable, instead, your enum should be exposed as a Property.
public IEnumerable<StrategyTypes> StrategyTypes
{
get
{
return Enum.GetValues(typeof(StrategyType)).Cast<StrategyType>();
}
}
Also, Discosultan has already solved another problem for you.
Simplest way to bind any enum data to combobox in wpf XAML:
Add data provider in window or user control resource
xmlns:pro="clr-namespace:TestProject">
<UserControl.Resources>
<ObjectDataProvider x:Key="getDataFromEnum" MethodName="GetValues" ObjectType="{x:Type System:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="pro:YourEnumName"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</UserControl.Resources>
<!--ComboBox xaml:-->
<ComboBox ItemsSource="{Binding Source={StaticResource getDataFromEnum}}"/>
I have found a solution on youtube. Check the link below:
How to Bind an Enum to a ComboBox in WPF: https://youtu.be/Bp5LFXjwtQ0
This solution creates a new class derived from MarkupExtension class and uses this class as a source in the XAML code.
EnumBindingSourceExtention.cs file:
namespace YourProject.Helper
{
public class EnumBindingSourceExtention : MarkupExtension
{
public Type EnumType { get; private set; }
public EnumBindingSourceExtention(Type enumType)
{
if (enumType == null || !enumType.IsEnum)
{
throw new Exception("EnumType is null or not EnumType");
}
this.EnumType = enumType;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return Enum.GetValues(EnumType);
}
}
}
View.Xaml file:
<Window
xmlns:helper="clr-namespace:YourProject.Helper"/>
<ComboBox
ItemsSource="{Binding Source={helper:EnumBindingSourceExtention {x:Type local:TheEnumClass}}}" />
local:TheEnumClass: TheEumClass should be located where you specify the namespace (in this case, it is on local)
I am writing an application for Windows Phone 8.1, and I wanted to use a flyout on listView item. Because I am doing my best to write nice app, I am trying to use MVVM pattern and resource dictionaries with templates insead of all xaml in one page.
However, I can't bind my MenuFlyoutItem Command - it seems like it doesn't see the datacontext of the page, or it has some other dataContext. Here is some code:
1) My template in a separate resource dictionary:
<Grid Margin="0, 0, 0, 10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="4*" />
</Grid.ColumnDefinitions>
<Grid.Resources>
<converters:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
<converters:EmptyDateConverter x:Key="EmptyDateConverter" />
</Grid.Resources>
<i:Interaction.Behaviors>
<icore:EventTriggerBehavior EventName="Holding">
<converters:OpenMenuFlyoutAction />
</icore:EventTriggerBehavior>
</i:Interaction.Behaviors>
<FlyoutBase.AttachedFlyout>
<MenuFlyout>
<MenuFlyoutItem x:Uid="AddToCalendarMenuItem" Command="{Binding AddToCalendar}" />
</MenuFlyout>
</FlyoutBase.AttachedFlyout>
<Image Grid.Column="0" Source="{Binding ThumbnailUri}"/>
<StackPanel Grid.Column="1" Orientation="Vertical" Margin="10,0,0,0">
<TextBlock Text="{Binding Title}" Style="{StaticResource ListItemTitle}"/>
<StackPanel Orientation="Horizontal" VerticalAlignment="Bottom">
<TextBlock x:Uid="DvdReleaseDate" />
<TextBlock Text="{Binding DvdRelease, Converter={StaticResource EmptyDateConverter}}" />
</StackPanel>
</StackPanel>
</Grid>
2) And here is the list view:
<ListView Grid.Row="1" x:Name="SearchListView"
ItemsSource="{Binding SearchList}"
ItemTemplate="{StaticResource SearchListTemplate}" SelectionChanged="NavigateToMovieDetails" />
My ViewModel is a static kind of singleton in the app.xaml.cs
I've tried to create a new instance of the VM in xaml, but it didn't work - maybe I was doing smth wrong.
I would really appreciate Your help! Thanks in advance.
Best regards,
Roman
If you stick with that <ItemTemplate> you will have to have a Command per every Model in your ViewModel, which is not ideal.
To set a single Command and have a CommandParameter see this SO Tutorial I made it is too much code to type here:
Implement a ViewModel Single Command with CommandParamater
#Chubosaurus Software following your approach I came up with this.
Here is a ListView bound to a list of todo items placed inside a DataTemplate which contains a TextBlock having a MenuFlyout to show edit, delete context menu kind of thing.
The key to bind the commands in the view model to the MenuFlyoutItem is to give the ListView a name and do element binding using the ElementName property in the Command to point to the ListView's name. To access the commands in our view model we've to go through the ListView's DataContext and bind it to a command on it because the DataContext of the MenuFlyoutItem is an item in the ItemsSource
The MainPage.xaml
<Page
x:Class="UWA.MenuFlyout.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:UWA.MenuFlyout"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:UWA.MenuFlyout.ViewModels"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
xmlns:common="using:UWA.MenuFlyout.Core"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid Margin="24,24">
<ListView x:Name="Todos" ItemsSource="{Binding Todos}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Action}">
<FlyoutBase.AttachedFlyout>
<MenuFlyout>
<MenuFlyoutItem Text="edit"
Command="{Binding ElementName=Todos, Path=DataContext.EditTodo}"
CommandParameter="{Binding}"/>
<MenuFlyoutItem Text="delete"
Command="{Binding ElementName=Todos, Path=DataContext.DeleteTodo}"
CommandParameter="{Binding}"/>
</MenuFlyout>
</FlyoutBase.AttachedFlyout>
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Holding">
<common:OpenMenuFlyoutAction/>
</core:EventTriggerBehavior>
<core:EventTriggerBehavior EventName="RightTapped">
<common:OpenMenuFlyoutAction/>
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Page>
The MainPage.xaml.cs is where the DataContext of the MainPage is set.
namespace UWA.MenuFlyout
{
using UWA.MenuFlyout.ViewModels;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
this.NavigationCacheMode = NavigationCacheMode.Required;
this.DataContext = new MainViewModel();
}
/// <summary>
/// Invoked when this page is about to be displayed in a Frame.
/// </summary>
/// <param name="e">Event data that describes how this page was reached.
/// This parameter is typically used to configure the page.</param>
protected override void OnNavigatedTo(NavigationEventArgs e)
{
// TODO: Prepare page for display here.
// TODO: If your application contains multiple pages, ensure that you are
// handling the hardware Back button by registering for the
// Windows.Phone.UI.Input.HardwareButtons.BackPressed event.
// If you are using the NavigationHelper provided by some templates,
// this event is handled for you.
}
}
}
The MainViewModel.cs containing the Todos which is an ObservableCollection type and the EditTodo and DeleteTodo commands.
namespace UWA.MenuFlyout.ViewModels
{
using System.Collections.ObjectModel;
using System.Windows.Input;
using UWA.MenuFlyout.Core;
using UWA.MenuFlyout.Models;
public class MainViewModel : BaseViewModel
{
private ICommand editTodo;
private ICommand deleteTodo;
public MainViewModel()
{
this.Todos = new ObservableCollection<TodoModel>
{
new TodoModel { Id = 1, Action = "Buy Milk", IsDone = true },
new TodoModel { Id = 2, Action = "Buy Groceries", IsDone = false }
};
}
public ObservableCollection<TodoModel> Todos { get; set; }
public ICommand EditTodo
{
get
{
if (this.editTodo == null)
{
this.editTodo = new RelayCommand(this.OnEditTodo);
}
return this.editTodo;
}
}
public ICommand DeleteTodo
{
get
{
if (this.deleteTodo == null)
{
this.deleteTodo = new RelayCommand(this.OnDeleteTodo);
}
return this.deleteTodo;
}
}
public void OnEditTodo(object parameter)
{
// perform edit here
var todo = parameter as TodoModel;
}
public void OnDeleteTodo(object parameter)
{
// perform delete here
var todo = parameter as TodoModel;
}
}
}
The Model
namespace UWA.MenuFlyout.Models
{
public class TodoModel
{
public int Id { get; set; }
public string Action { get; set; }
public bool IsDone { get; set; }
}
}
The BaseViewModel which implements the INotifyPropertyChanged.
namespace UWA.MenuFlyout.Core
{
using System.ComponentModel;
using System.Runtime.CompilerServices;
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
A simple ICommand implementation.
namespace UWA.MenuFlyout.Core
{
using System;
using System.Windows.Input;
public class RelayCommand : ICommand
{
private Action<object> action;
public RelayCommand(Action<object> action)
{
this.action = action;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
this.action(parameter);
}
}
}
The OpenMenuFlyoutAction which implements DependencyObject and IAction to open the MenuFlyout by using the Execute method on the IAction interface.
namespace UWA.MenuFlyout.Core
{
using Microsoft.Xaml.Interactivity;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls.Primitives;
public class OpenMenuFlyoutAction : DependencyObject, IAction
{
public object Execute(object sender, object parameter)
{
var frameworkElement = sender as FrameworkElement;
var flyoutBase = FlyoutBase.GetAttachedFlyout(frameworkElement);
flyoutBase.ShowAt(frameworkElement);
return null;
}
}
}
I am having trouble binding to the ItemsSource of a List box control. I would like to be able to add text lines to the List box when the user preforms certain actions.
The SystemControls.xmal Code:
<ListBox Grid.Column="4" Grid.Row="1" Grid.RowSpan="9" ItemsSource="{Binding ListBoxInput}" Height="165" HorizontalAlignment="Left" Name="listBox1" VerticalAlignment="Top" Width="250" ></ListBox>
The SystemControls.xmal.cs code snippet:
public partial class SystemControls : UserControl, ISystemControls
{
IDriver _Driver;
ISystemControls_VM _VM;
public SystemControls(IDriver InDriver, ISystemControls_VM InVM)
{
_VM = InVM;
_Driver = InDriver;
DataContext = new SystemControls_VM(_Driver);
InitializeComponent();
}
The SystemControls_VM.cs This should be where the heart of the problem is. I have gotten it to work in the constructor, when i try to add lines later in the code, for example when a user press a button, it does nothing:
public class SystemControls_VM:ViewModelBase, ISystemControls_VM
{
IDriver _Driver;
public ObservableCollection<string> _ListBoxInput = new ObservableCollection<string>();
public SystemControls_VM(IDriver InDriver)
{
_Driver = InDriver;
ListBoxInput.Add("test");//Works here
}
public ObservableCollection<string> ListBoxInput
{
get
{
return _ListBoxInput;
}
set
{
_ListBoxInput = value;
//OnPropertyChanged("ListBoxInput");
}
}
public void OnButtonClickGetNextError()
{
ListBoxInput.Add("NextErrorClicked");//Does not work here
}
public void OnButtonClickClear()
{
ListBoxInput.Clear();//Or Here
}
Also in case it's needed the OnPropertyChangedEventHandler:
namespace XXX.BaseClasses.BaseViewModels
{
/// <summary>
/// Provides common functionality for ViewModel classes
/// </summary>
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate{};
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
1) Your public property is called _ListBoxInput but you're binding to ListBoxInput (no underscore). Make _ListBoxInput private.
2) Because the collection is already observable, you don't need the OnPropertyChanged for your listbox to update.
3) It looks like something might be off with the way you're managing your public vs private ListBoxInput collections. You're calling .Add on your public property (which will immediately raise an event on the observable collection) but then you'll end up adding it to the private collection as well, and then you're calling PropertyChanged on the public property. It's confusing: try my code below and see how it works. (Note in your constructor you add to _ListBoxInput but in your button click event you add to ListBoxInput.)
4) Try adding this.DataContext = this in your constructor
public partial class MainWindow : Window {
public ObservableCollection<string> ListBoxInput { get; private set; }
public MainWindow() {
InitializeComponent();
this.ListBoxInput = new ObservableCollection<string>();
this.DataContext = this;
}
private void AddListBoxEntry_Click(object sender, RoutedEventArgs e) {
this.ListBoxInput.Add("Hello " + DateTime.Now.ToString());
}
}
and in the xaml, take a look at the binding Mode.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<ListBox ItemsSource="{Binding ListBoxInput, Mode=OneWay}"
Height="165" HorizontalAlignment="Left"
Name="listBox1" VerticalAlignment="Top" Width="250" />
<Button Grid.Column="1" Grid.Row="0" Name="AddListBoxEntry"
Margin="0,0,0,158" Click="AddListBoxEntry_Click" >
<TextBlock>Add</TextBlock>
</Button>
</Grid>
5) On a separate note, here's another way you could do your INotifyPropertyChanged (I find this cleaner)
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate{};
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
So got the answer from another source, but figured I would post it here for referance.
So what was happening was that I was setting the data context to one instance of SystemControls_VM while my _VM referance which was handling the button click was going to another instance of SystemControls_VM. That was also why it looked like the button click was working and the List was being populated but no data was getting to the Control itself
I changed the following section of code and it works:
public partial class SystemControls : UserControl, ISystemControls
{
IDriver _Driver;
SystemControls_VM _VM;
public SystemControls(IDriver InDriver, SystemControls_VM InVM)
{
_VM = InVM;
_Driver = InDriver;
DataContext = InVM;//new SystemControls_VM(_Driver);
InitializeComponent();
}