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!
Related
I used to just create a block of text by converting a list of strings to one string with newlines. This Binding worked; updated when it was supposed to and all, but I'm trying to move the list of text into an ItemsControl as they will need to be hyperlinks at some point in the future. Problem: The ItemsControl does not change when the PropertyChangeEvent is fired. The Relevant Code is as follows:
Xaml
<local:BaseUserControl x:Class="BAC.Windows.UI.Views.ErrorsView"
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:BAC.Windows.UI.Views"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
...
<ItemsControl ItemsSource="{Binding Path=ErrorMessages}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"></TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!--<TextBlock VerticalAlignment="Center" Visibility="{Binding ErrorMessages, Converter={StaticResource VisibleWhenNotEmptyConverter}}" Text="{Binding ErrorMessages, Converter={StaticResource ErrorMessagesToTextConverter}}">
(What I used to use)
</TextBlock>-->
...
</local:BaseUserControl>
ViewModel
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using ASI.Core.Core;
using ASI.Core.DTO;
using ASI.Core.Extensions;
using ASI.Core.Mappers;
using BAC.Core.Resources;
using BAC.Core.Services;
using BAC.Core.ViewModels.Views;
namespace BAC.Core.ViewModels
{
public interface IErrorsViewModel : IViewModel<IErrorsView>
{
}
public class ErrorsViewModel : BaseViewModel<IErrorsView>, IErrorsViewModel
{
...
private readonly ErrorDTO _errorDTO;
private readonly ErrorDTO _warningDTO;
public ErrorsViewModel(...) : base(view)
{
...
//Just added this string to know that it's at least binding. This Message displays, and never changes.
ErrorMessages = new List<string>() {"Simple Message"};
//Tells the View to bind dataContext to Viewmodel
Edit();
}
private void errorDTOOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
{
ErrorDTO dto;
if (!string.Equals(propertyChangedEventArgs.PropertyName, nameof(dto.HasError))) return;
ErrorMessages.Clear();
_errorDTO.ErrorMessages.Each(x => ErrorMessages.Add(Constants.Captions.Errors + ": " + x));
_warningDTO.ErrorMessages.Each(x => ErrorMessages.Add(Constants.Captions.Warnings + ": " + x));
OnPropertyChanged(() => ErrorMessages);
OnPropertyChanged(() => HasError);
OnPropertyChanged(() => HasWarning);
}
...
public bool HasError => _errorDTO.HasError;
public bool HasWarning => _warningDTO.HasError;
public IList<string> ErrorMessages { get; set; }
...
}
And just because I know people may ask to see it...
public class BaseNotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void OnPropertyChanged<T>(Expression<Func<T>> propertyExpression)
{
var body = propertyExpression.Body as MemberExpression;
if (body != null)
OnPropertyChanged(body.Member.Name);
}
protected void OnEvent(Action action)
{
try
{
action();
}
catch
{ }
}
}
I'm sure it's something stupidy simple I'm doing, but the harder I look, the more I get frusterated by what should something simple. Why does the binding work for all other conrols except ItemSource? What's so special about it?
I'll also add anotehr explanation (Even though I know this is old).
The reason this will not update the property is that the List object is not actually changing, so the ListView will not update the list. The only way to do this without using "ObservableCollection" is to create a brand new list on each property change like so:
private void errorDTOOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
{
if (!string.Equals(propertyChangedEventArgs.PropertyName, nameof(dto.HasError))) return;
OnPropertyChanged(() => ErrorMessages);
}
public List<string> ErrorMessages => getErrorMessages();
private List<string> getErrorMessages() {
//create list in a manner of your choosing
}
Hopefully that helps people when they run into this.
So I was able to get your code to work by using an ObservableCollection instead of the List. The ObservableCollection generates a list changed notification automatically when its collection is changed. Below is my sample code. I use a timer to update the error list every second.
<Window x:Class="TestEer.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:TestEer"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525" DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<ItemsControl ItemsSource="{Binding Path=ErrorMessages}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
using System.Collections.ObjectModel;
using System.Timers;
using System.Windows;
using System.Windows.Data;
namespace TestEer
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private Timer _timer;
private readonly object _sync = new object( );
public MainWindow( )
{
InitializeComponent( );
BindingOperations.EnableCollectionSynchronization( ErrorMessages, _sync );
_timer = new Timer
{
AutoReset = true,
Interval = 1000
};
_timer.Elapsed += _timer_Elapsed;
_timer.Enabled = true;
_timer.Start( );
}
private void _timer_Elapsed( object sender, ElapsedEventArgs e )
{
ErrorMessages.Add( $"Error # {e.SignalTime}" );
}
public ObservableCollection<string> ErrorMessages { get; } = new ObservableCollection<string>( );
}
}
We set up the OnPropertyChanged() method in the get set methods before the constructor and this seemed to work!
private bool _theString;
public bool TheString
{
get { return _theString; }
set { _theString = value; OnPropertyChanged(); }
}
Use {Binding TheString} in your .xaml.
Hope this helps!
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}" .../>
I have a textbox with a DependencyProperty, Code looks like this
<UserControl
x:Class="Projectname.Controls.Editors.EditTextControl"
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:ui="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
mc:Ignorable="d">
<Grid>
<TextBox PlaceholderText="I'am Active" HasError="{Binding IsInvalid, UpdateSourceTrigger=PropertyChanged}" Height="80" Width="300" x:Name="txtActive" Text="{Binding TextValue, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" ></TextBox>
</Grid>
public sealed partial class EditTextControl : UserControl
{
TestViewModel TV = new TestViewModel();
public EditTextControl()
{
this.InitializeComponent();
this.DataContext = TV;
}
public bool HasError
{
get { return (bool)GetValue(HasErrorProperty); }
set { SetValue(HasErrorProperty, value); }
}
/// <summary>
/// This is a dependency property that will indicate if there's an error.
/// This DP can be bound to a property of the VM.
/// </summary>
public static readonly DependencyProperty HasErrorProperty =
DependencyProperty.Register("HasError", typeof(bool), typeof(EditTextControl), new PropertyMetadata(false, HasErrorUpdated));
// This method will update the Validation visual state which will be defined later in the Style
private static void HasErrorUpdated(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
EditTextControl textBox = d as EditTextControl;
if (textBox != null)
{
if (textBox.HasError)
VisualStateManager.GoToState(textBox, "InvalidState", false);
else
VisualStateManager.GoToState(textBox, "ValidState", false);
}
}
}
All looks good to me, But on compile-time itself, it's giving these errors.
The property 'HasError' was not found in type 'TextBox'.
The member "HasError" is not recognized or is not accessible.
Can anyone please point out what I am doing wrong here?
HasError is a property on the EditTextControl user control, not on the TextBox.
If you want to add a custom property to the TextBox class, you use an Attached Property not a Dependency property.
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.
I have a UserControl that includes three TextBlock controls. I want to implement three custom properties in UserControl. Something like:
public partial class MyControl: UserControl
{
...
public String Title
{
get { return this.textBlock1.Text; }
set { this.textBlock1.Text = value; }
}
public String Units
{
get { return this.textBlock2.Text; }
set { this.textBlock2.Text = value; }
}
public String Data
{
get { return this.textBlock3.Text; }
set { this.textBlock3.Text = value; }
}
}
If I want to use binding capabilities with these properties I have to implement them as dependency properties. Am I right? But I do not know how to do it in my case.
That is correct. Binding to dependency properties is quite simple to do. Understanding the mechanics I would suggest looking through MSDN. However to answer your question you provide static dependency properties registered to the user control. Then your getters \ setters reference the property.
Here is a sample line of a dependency property.
/// <summary>
/// Provides a bindable text property to the user control
/// </summary>
public static DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(UserControl1), new PropertyMetadata("", onTextPropertyChanged));
/// <summary>
/// optional static call back handler when the property changed
/// </summary>
/// <param name="o"></param>
/// <param name="e"></param>
static void onTextPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var obj = o as UserControl1;
if (obj == null)
return;
//TODO: Changed...
}
/// <summary>
/// Gets \ sets the text
/// </summary>
public string Text
{
get { return (string)this.GetValue(TextProperty); }
set
{
if (this.Text != value)
this.SetValue(TextProperty, value);
}
}
The above is very simple. We register a dependency property TextProperty to UserControl1, this property is the type of string and has a default value of "" (as noted in the property meta data). I also provided a static callback handler if you wish to perform additional steps once the property has changed.
You will then see the Text property uses the GetValue() and SetValue() methods for getting and setting the value of the Text Property.
UPDATE: Binding to a child element in XAML.
This update is to show how to use the the above TextProperty for binding.
Usercontrol1.Xaml. This is the XAML for UserControl1.
<UserControl x:Class="WpfApplication1.UserControl1"
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"
mc:Ignorable="d"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Label Grid.Column="0" FontWeight="Bold" Content="Text" VerticalAlignment="Center" />
<TextBox Text="{Binding Text, Mode=TwoWay}" Grid.Column="1" VerticalAlignment="Center" Padding="4" />
</Grid>
</UserControl>
My Main Window View Model (Implement INotifyPropertyChanged)
public class MainWindowModel : INotifyPropertyChanged
{
/// <summary>
/// the text
/// </summary>
string myProperty = "This is the default text";
/// <summary>
/// Gets \ sets the text
/// </summary>
public string MyProperty
{
get { return this.myProperty; }
set
{
if (this.MyProperty != value)
{
this.myProperty = value;
this.OnPropertyChanged("MyProperty");
}
}
}
/// <summary>
/// fires the property changed event
/// </summary>
/// <param name="propertyName"></param>
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
/// <summary>
/// the property changed event
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
}
MainWindow.Xaml. Binding the text property to the view model
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ctrl="clr-namespace:WpfApplication1"
Name="Window1"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<ctrl:MainWindowModel />
</Window.DataContext>
<Grid>
<ctrl:UserControl1 Text="{Binding Path=DataContext.MyProperty, Mode=TwoWay, ElementName=Window1}" />
</Grid>
</Window>
Code for the dependency property :
public string Title
{
get { return (string)this.GetValue(TitleProperty); }
set { this.SetValue(TitleProperty, value); }
}
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title",
typeof(string),
typeof(MyControl),
new PropertyMetadata(null));
Binding in xaml :
<TextBlock Text="{Binding Title,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type yourXmlns:MyControl}}"/>