Add a space in column header while binding ObservableCollection to a Grid - wpf

I want to add a space in my column header when I am binding my ObservableCollection to a Grid in MVVM way. Here is my code..
public class Site
{
public string Name { get; set; }
public string Description { get; set; }
[Description("Short Code")]
[Bindable(true)]
public string ShortCode { get; set; }
public static ObservableCollection<Site> GetSampleData()
{
var sites = new ObservableCollection<Site>();
sites.Add(new Site { Name = "New Delhi", Description = "New Delhi Railway Station", ShortCode = "NDLS"});
sites.Add(new Site { Name = "Mumbai", Description = "Mumbai Railway Station", ShortCode = "MUM"});
return sites;
}
}
// Here is my View Model Class
public class SiteViewModel
{
public ObservableCollection<Site> SitesDataCollection { get; set; }
public SiteViewModel()
{
SitesDataCollection = Site.GetSampleData();
}
}
// This is the View Class
public partial class SiteView : UserControl
{
public Sites()
{
InitializeComponent();
DataContext = new SiteViewModel();
}
}
My XAML is
<Grid>
<DockPanel>
<DataGrid Name="SiteGrid" ItemsSource="{Binding Path=SitesDataCollection}">
</DataGrid>
</DockPanel>
</Grid>
Basically in my Grid, for the third column , the header is ShortCode, I want to display "Short Code", How can I do that.
I tried adding
[Description("Short Code")] and
[Bindable(true)]
but didn't got the desired result.

In the AutoGeneratingColumn event handler of the DataGrid, access the DataGridColumn properties by referencing the DataGridAutoGeneratingColumnEventArgs.Column property. Use this to edit the column header.
private void DataGrid_OnAutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
e.Column.Header = Regex.Replace(e.Column.Header.ToString(), "(\\B[A-Z])", " $1");
}
This will add spaces into your property names where there are upper case letters.

Related

WPF filtered dynamic menu

I'm working on getting a UserControl in WPF working that has a MenuItem populated with an ItemsSource, which creates a menu that goes n levels deep (although I'm just looking at TopMenuItem\Branches\Leaves right now).
The wrinkle I'm having trouble with is that I want to filter the leaves through a textbox embedded into the menu. If a branch has no leaves, it also gets filtered out. It looks like this at the moment :
I'm working with an ObservableCollection of IMenuTreeItem, which can contain branches (which in turn also has an ObservableCollection of IMenuTreeItem) or leaves.
public interface IMenuTreeItem
{
string Name { get; set; }
}
public class MenuTreeLeaf : IMenuTreeItem
{
public string Name { get; set; }
public Guid UID { get; set; }
public ObjectType Type { get; set; }
public Requirement Requirement { get; set; }
public MenuTreeLeaf(string name, ObjectType type, Guid uID)
{
Type = type;
Name = name;
UID = uID;
}
public MenuTreeLeaf(string name)
{
Name = name;
}
}
public class MenuTreeBranch : IMenuTreeItem, INotifyPropertyChanged
{
public string Name { get; set; }
private ObservableCollection<IMenuTreeItem> _items;
public ObservableCollection<IMenuTreeItem> Items
{
get
{
return _items;
}
set
{
_items = value; OnPropertyChanged();
}
}
public MenuTreeBranch(string name)
{
Name = name;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
This is how I'm filtering. It very much feels like there's a better way.
ObservableCollection<IMenuTreeItem> result = new ObservableCollection<IMenuTreeItem>(ItemsSource);
for (int i = 0; i < result.Count; i++)
{
if (result[i] is MenuTreeBranch currentBranch)
{
if (currentBranch.Items != null)
currentBranch.Items = new ObservableCollection<IMenuTreeItem>(currentBranch.Items.Where(x => x.Name.ToLower().Contains(SearchField.ToLower())));
}
}
result = new ObservableCollection<IMenuTreeItem>(result.Where(x => (x as MenuTreeBranch).Items.Count > 0));
result.Insert(0, new MenuTreeLeaf("[Search]"));
return result;
So my main problems are:
When I've filtered, I can no longer unfilter. ItemsSource gets changed too. Could it be because I'm filtering in the ItemsSourceFiltered getter? I tried to clone, but eh, didn't change anything
When I call OnPropertyChanged on ItemsSourceFiltered any time text changes in the textbox, the menu closes. The menu definitely shouldn't close while you're inputting text.
Any advice?
You may have a menu item class that exposes a recursive Filter string and a collection property that returns the filtered child items:
public class FilteredMenuItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string Name { get; set; }
public ICommand Command { get; set; }
private string filter;
public string Filter
{
get { return filter; }
set
{
filter = value;
foreach (var childItem in ChildItems)
{
childItem.Filter = filter;
}
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Filter)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FilteredChildItems)));
}
}
public List<FilteredMenuItem> ChildItems { get; set; } = new List<FilteredMenuItem>();
public IEnumerable<FilteredMenuItem> FilteredChildItems
{
get { return string.IsNullOrEmpty(Filter)
? ChildItems
: ChildItems.Where(childItem => (bool)childItem.Name?.Contains(Filter)); }
}
}
With a RootItem property in the view model like
public FilteredMenuItem RootItem { get; }
= new FilteredMenuItem { Name = "Items" };
you may bind to it in XAML like this:
<StackPanel DataContext="{Binding RootItem}">
<TextBox Text="{Binding Filter, UpdateSourceTrigger=PropertyChanged}"/>
<Menu>
<Menu.Resources>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Name}"/>
<Setter Property="Command" Value="{Binding Command}"/>
<Setter Property="ItemsSource"
Value="{Binding FilteredChildItems}"/>
</Style>
</Menu.Resources>
<MenuItem/>
</Menu>
</StackPanel>
While you populate the ChildItems property of each FilteredMenuItem, the view only shows the FilteredChildItems collection.
You may also notice that the above doesn't use ObservableCollection at all, since no items are added to or removed from any collection at runtime. You just have to make sure the item tree is populated before the view is loaded.

WPF Combo Box not displaying dynamically changed selected item

I am trying to change the selected item of a combo box based on a change in another combo box. The situation is complicated by the fact that both combo boxes appear in a list of item templates.
The XAML is as follows:
<ListBox ItemsSource="{Binding AncillaryExtendedPropertyViewModels}" ItemTemplateSelector="{StaticResource templateSelector}"/>
<DataTemplate x:Key="EnumDataTemplate"> <Grid Margin="4"
MinHeight="25"> <ComboBox SelectedItem="{Binding ExtendedPropertyEnum,
UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
ItemsSource="{Binding ExtendedPropertyEnumList}"
DisplayMemberPath="Value"/> </Grid> </DataTemplate>
The data context of the view containing the XAML is set to AncillaryBaseViewModel. The following is a cut down version of AncillaryBaseViewModel.
public class AncillaryBaseViewModel : ComplexOrderItemViewModel, IDataErrorInfo
{
private ObservableCollection<ExtendedPropertyViewModel> _ancillaryExtendedPropertyViewModels;
public ObservableCollection<ExtendedPropertyViewModel> AncillaryExtendedPropertyViewModels
{
get { return _ancillaryExtendedPropertyViewModels; }
set
{
_ancillaryExtendedPropertyViewModels = value;
OnPropertyChanged("AncillaryExtendedPropertyViewModels");
}
}
and the ExtendedPropertyViewModel class....
public class ExtendedPropertyViewModel : DataTemplateSelector
{
private ExtendedProperty _extendedProperty;
public DataTemplate DefaultnDataTemplate { get; set; }
public DataTemplate BooleanDataTemplate { get; set; }
public DataTemplate EnumDataTemplate { get; set; }
public ExtendedPropertyEnum ExtendedPropertyEnum
{
get
{ return ExtendedProperty.ExtendedPropertyEnum; }
set
{
if (ExtendedProperty.ExtendedPropertyEnum != value)
{
_extendedProperty.ExtendedPropertyEnum = value;
AncillaryBaseViewModel parent = RequestParent();
if (parent != null)
{
parent.AncillaryExtendedPropertyViewModels[7].ExtendedPropertyEnum =
ExtendedProperty.ExtendedPropertyEnum.DependentExtendedPropertyEnums[0];
}
parent.OrderItem.SetStockCode();
PropertyChanged += parent.OnExtendedPropertyChanged;
OnPropertyChanged();
}
}
}
and the ExtendedProperty class....
public class ExtendedProperty : ViewModelBase
{
private ExtendedPropertyEnum _extendedPropertyEnum;
public int ExtendedPropertyID { get; set; }
public int OrderItemTypeID { get; set; }
public string DisplayName {get; set;}
public ObservableCollection<ExtendedPropertyEnum> ExtendedPropertyEnumList { get; set; }
public string Value { get; set; }
public ExtendedPropertyEnum ExtendedPropertyEnum
{
get
{
return _extendedPropertyEnum;
}
set
{
_extendedPropertyEnum = value;
OnPropertyChanged("ExtendedPropertyEnum");
}
}
}
To summarise, when I change the value of combo box A through the UI, this calls the ExtendedPropertyEnum setter within ExtendedPropertyViewModel, which changes the ExtendedPropertyEnum bound to another combo box B, which is in the same list. I would expect this to update the value displayed in combo box B accordingly, which it does not.
As an aside, changing the value of combo box A does update a label that is not within a data template. The XAML for this label is....
<Label Content="{Binding StockCode}" MinWidth="100"/>
This is updated by the following code within AncillaryBaseViewModel....
public void OnExtendedPropertyChanged(object sender, EventArgs args)
{
OnPropertyChanged("StockCode");
}
I thought I could change this to the following to force the combo boxes in the list to update.
public void OnExtendedPropertyChanged(object sender, EventArgs args)
{
OnPropertyChanged("StockCode");
OnPropertyChanged("AncillaryExtendedPropertyViewModels");
}
However, this did not work either.
Any help greatly appreciated.
Roger.
if I understand the question correctly then you are expecting a changed value within an observablecollection to be reflected within the UI. This will not happen. observablecollections raise notifyproperty events when the collection itself changes, not when values within them change. You'll either need to raise a notifyproperty event on the value changing or remove the item from the list and add it back with a new value.

How to bind a textbox.Text property in WPF MMVM design

So I'm trying to learn the MVVM design patter in WPF, I want to do the following:
In external class I've got a ObservableCollection _students that is bound to a listview on the WPF window using MVVM design pattern. The listview shows only the Student's name and Age.
public class Student
{
public string Name { get; set; }
public int Age { get; set; }
public string Course { get; set; }
public DateTime JoiningDate { get; set; }
}
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<Student> _students;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged!=null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public ObservableCollection<Student> Students
{
get
{
return _students;
}
set
{
_students = value;
NotifyPropertyChanged("Students");
}
}
All good, but I want to put a TextBox and set it to show the listview's selected item's course property. This means I must
get the listview's selected index (ok)
bind the textbox.Text property to Students[that index].Course
I'm stuck at 2. Any help?
i would solve this by another way.
Take a look at this post
.
Another way would be that your ViewModel contains a Student-property(e.g. SelectedStudent) which is bind to the SelctedItem of the listView. Then you can handel this by
{Binding Path=SelectedStudent.Course}
Assume you bind the listview to a collection of type SampleData like below:
SampleData.cs
public class SampleData
{
public int Id { get; set; }
public string Text { get; set; }
public decimal Value { get; set; }
}
Then you bind the ListView ItemsSource to a collection. WIt does not matter if you bind ItemsSource property to a property on ViewModel or you bind it in code-behind like the code below.
var source = new List<SampleData>();
source.Add(new SampleData() { Id = 1, Text = "AAA" });
source.Add(new SampleData() { Id = 2, Text = "BBB" });
source.Add(new SampleData() { Id = 3, Text = "CCC" });
source.Add(new SampleData() { Id = 4, Text = "DDD" });
source.Add(new SampleData() { Id = 5, Text = "EEE" });
You can bind TextBox's Text property to the SelectedItem directly on the View.
<StackPanel Orientation="Horizontal">
<ListView x:Name="listView1" />
<TextBox Text="{Binding ElementName=listView1, Path=SelectedItem.Text}" />
</StackPanel>

Simple Windows Phone User Control Databinding Does Not Work

I have an issue with something that should be very simple databinding scenario. I want to bind a list of items. I want to create a user control put it in a ItemsControl's template and bind the ItemsControl to some data. I am perfectly happy with one time databinding so I was kind of hoping to avoid learning about dependency properties and all the databinding stuff for this simple scenario.
Here is the XAML for the user control:
<TextBlock>Just Something</TextBlock>
And the code behind:
namespace TestWindowsPhoneApplication
{
public partial class TestControl : UserControl
{
public TestData SomeProperty { get; set; }
public String SomeStringProperty { get; set; }
public TestControl()
{
InitializeComponent();
}
}
}
MainPage.xaml:
<ItemsControl Name="itemsList" ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<t:TestControl SomeStringProperty="{Binding Path=SomeString}"></t:TestControl>
<!--<TextBlock Text="{Binding Path=SomeString}"></TextBlock>-->
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Here is MainPage.xaml.cs:
namespace TestWindowsPhoneApplication
{
public class TestData
{
public string SomeString { get; set; }
}
public partial class MainPage : PhoneApplicationPage
{
// Constructor
public MainPage()
{
InitializeComponent();
itemsList.DataContext = new TestData[] { new TestData { SomeString = "Test1" }, new TestData { SomeString = "Test2" } };
}
}
}
When I run the project I get an error "the parameter is incorrect". I also tried binding directly to the item with SomeProperty={Binding} since that is what I actually want to do but this causes the same error. If I try doing the same thing with the TextBlock control (the commented line) everything works fine.
How can I implement this simple scenario?
To make a property on your custom control "bindable" you have to make it a dependency property. Check out my answer here for a nice simple example of doing just this on a custom control: passing a gridview selected item value to a different ViewModel of different Usercontrol
public string SomeString
{
get { return (string)GetValue(SomeStringProperty); }
set { SetValue(SomeStringProperty, value); }
}
public static readonly DependencyProperty SomeStringProperty =
DependencyProperty.Register("SomeString", typeof(string), typeof(TestControl),
new PropertyMetadata(string.Empty, new PropertyChangedCallback(OnSomeStringChanged)));
private static void OnSomeStringChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((TestControl)d).OnSomeStringChanged(e);
}
protected virtual void OnSomeStringChanged(DependencyPropertyChangedEventArgs e)
{
//here you can do whatever you'd like with the updated value of SomeString
string updatedSomeStringValue = e.NewValue;
}

WPF User Control

I want to have a User Control that takes a collection of People (property "Data") and displays them in a list box.
When I run my app nothing shows in the listbox. Can you please point out what I'm doing wrong?
Thanks!!!
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public override string ToString()
{
return Name + "(" + Age + ")";
}
}
User Control:
(uc1.xaml.cs)
public partial class uc1
{
public static readonly DependencyProperty DataProperty = DependencyProperty.Register("Data", typeof (List<Person>), typeof (uc1));
public List<Person> Data
{
get { return (List<Person>) GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
public uc1()
{
InitializeComponent();
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
DataContext = Data;
}
}
(uc1.xaml)
<ListBox ItemsSource="{Binding Name}" />
The ItemsSource property controls the list of items that are displayed in the listbox. If you want the ListBox to display one line for each person, you need to set the ItemsSource to bind directly to the DataContext. Then you use the DisplayMemberPath property to control which property of the Person class to show.
Here's my sample code that works for me.
The person class is the same.
The Window1.xaml.cs:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
List<Person> Data = new List<Person>();
Data.Add(new Person { Name = "Test 1", Age = 5 });
Data.Add(new Person { Name = "Test 2", Age = 10 });
this.DataContext = Data;
}
}
The Window1.xaml
<ListBox ItemsSource="{Binding}" DisplayMemberPath="Name" />

Resources