WPF Multi-TextBoxes User Control - wpf

I want to have a user control[say UC1] comprising 4 text boxes [say tb1,tb2,tb3, and tb4]. This user control should have 4 normal properties [say prop1, prop2, prop3, and prop4] binding to these text boxes. I want a dependency property [say dp] exposed to outer world by this user control.
This user control gets a single string [say 0\abc|1\def|2\ghi|3\jkl] from a property[say StrProp] of class [say C1] and is splitted into 4 parts[say abc, def, ghi, and jkl] to display in 4 text boxes of my user control. If any changes done by user in any or all textboxes, all the changed texts should be combined and reflected back to class C1\StrProp property.
Also, my requirement is that dp should be bounded to StrProp in UI\XAML. Validations should also be done properly.
Can anyone please help me by writing an example?
Sample classes are as below:
MyMultiTextBoxUserControl.xaml
<UserControl x:Class="MyMultiTextBoxControl_UsingNConsuming.MyMultiTextBoxUserControl"
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">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height=".25*"/>
<RowDefinition Height=".25*"/>
<RowDefinition Height=".25*"/>
<RowDefinition Height=".25*"/>
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Text="{Binding ElementName=UserControl, Path=CombinedField1 }"/>
<TextBox Grid.Row="1" Text="{Binding ElementName=UserControl, Path=CombinedField2}"/>
<TextBox Grid.Row="2" Text="{Binding ElementName=UserControl, Path=CombinedField3}"/>
<TextBox Grid.Row="3" Text="{Binding ElementName=UserControl, Path=CombinedField4}"/>
</Grid>
</UserControl>
MyMultiTextBoxUserControl.xaml.cs
public partial class MyMultiTextBoxUserControl : UserControl
{
public MyMultiTextBoxUserControl()
{
InitializeComponent();
}
//static FrameworkPropertyMetadata propertydata = new FrameworkPropertyMetadata("Hello",
// FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(PropertyChanged_Callback), new CoerceValueCallback(CoerceValue_Callback),
// false, UpdateSourceTrigger.LostFocus);
//public static readonly DependencyProperty CombinedTextProperty =
// DependencyProperty.Register("CombinedText", typeof(string), typeof(MyMultiTextBoxUserControl), propertydata, new ValidateValueCallback(Validate_ValueCallback));
static FrameworkPropertyMetadata propertydata = new FrameworkPropertyMetadata("Hello",
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(PropertyChanged_Callback));
public static readonly DependencyProperty CombinedTextProperty =
DependencyProperty.Register("CombinedText", typeof(string), typeof(MyMultiTextBoxUserControl), propertydata);
private static bool Validate_ValueCallback(object value)
{
string str=value as string;
bool result = true;
if (str.Length > 28)
result = false;
if (str.Length < 1)
result = false;
if (str.Substring(0, 2) != "0'\'")
result = false;
if (str.Contains("1'\'") == false || str.Contains("2'\'") || str.Contains("3'\'"))
result = false;
return result;
}
private static object CoerceValue_Callback(DependencyObject obj,object value)
{
return value;
}
private static void PropertyChanged_Callback(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
MyMultiTextBoxUserControl control=(MyMultiTextBoxUserControl)obj;
string select = e.NewValue.ToString();
char[] pipeDelim,slashDelim;
string[] pipeSplt;
pipeDelim = new char[] { '|' };
slashDelim = new Char[] { '/' };
pipeSplt = select.Split(pipeDelim);
if (pipeSplt.Length == 1)
return;
string[][] str = new string[4][];
int x = 0;
foreach (string s in pipeSplt)
{
if (string.IsNullOrEmpty(s) == false)
{
str[x] = s.Split(slashDelim);
x++;
}
}
control.CombinedField1 = str[0][1];
control.CombinedField2 = str[1][1];
control.CombinedField3 = str[2][1];
control.CombinedField4 = str[3][1];
}
public string CombinedText
{
get { return GetValue(CombinedTextProperty) as string; }
set { SetValue(CombinedTextProperty, value); }
}
public string CombinedField1
{
get; set;
}
public string CombinedField2
{
get;
set;
}
public string CombinedField3
{
get;
set;
}
public string CombinedField4
{
get;
set;
}
}
CombinedStringClass.cs
namespace MyMultiTextBoxControl_UsingNConsuming
{
public class CombinedStringClass
{
public CombinedStringClass() { }
string m_CombinedString;
public string CombinedString
{
get { return m_CombinedString; }
set
{
if (m_CombinedString != value)
m_CombinedString = value;
}
}
}
}
ConsumerClass.xaml
<Window x:Class="MyMultiTextBoxControl_UsingNConsuming.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyMultiTextBoxControl_UsingNConsuming;assembly="
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:CombinedStringClass x:Key="myClass"/>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.5*"/>
<ColumnDefinition Width="0.5*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="0.33*"/>
<RowDefinition Height="0.34*"/>
<RowDefinition Height="0.33*"/>
</Grid.RowDefinitions>
<TextBlock Text="User Control Text Boxes" Grid.Row="0" Grid.Column="0" Foreground="Black" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<local:MyMultiTextBoxUserControl Grid.Row="0" Grid.Column="1" Foreground="Black" CombinedText="{Binding Source=myClass, Path=CombinedString, Mode=TwoWay,FallbackValue=DataNotBound}"/>
<TextBlock Text="Combied String" Grid.Row="2" Grid.Column="0" Foreground="Black" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<TextBox Name="OneStringTextBox" Grid.Row="2" Grid.Column="1" Foreground="Black" Text="0\abc|1\def|2\ghi|3\jkl" IsEnabled="False"/>
</Grid>
</Window>
I also need to combine the changed texts ofUserControl's textboxes in such a way that it should be in a form of [0\f|1\gh|2\zx|3\oo] to be reflected in OneStringTextBox. Also, total string length should be 28 & max length of each textbox is 7.

Read WPF in C# 2010: Windows Presentation Foundation in .NET 4 Matthew MacDonald Chapter 18.
There is a great example that shoud help you.
Give name to your User control, replace {Binding ElementName=UserControl... with {Binding ElementName=NameOfUserControl, convert CombinedFields properties to DPs.

Related

How to Register an Attached Property of Textbox Text So Datagrid Can Update on Change

I have a textbox and a datagrid like so:
<Page
TextElement.FontSize="14" FontFamily="Segoe UI"
Title="Delivery View">
<Page.Resources>
<xcdg:DataGridCollectionViewSource x:Key="firstNameDataSource"
Source="{Binding Path=Accessor.Views[FirstNameView].SourceCollection}"
AutoFilterMode="And"
DistinctValuesConstraint="Filtered">
<xcdg:DataGridCollectionViewSource.ItemProperties>
<xcdg:DataGridItemProperty Name="FirstName" CalculateDistinctValues="False"/>
</xcdg:DataGridCollectionViewSource.ItemProperties>
</xcdg:DataGridCollectionViewSource>
</Page.Resources>
<ScrollViewer Name="pendingScroll" HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible">
<DockPanel Name="pnlMainPanel" LastChildFill="True" Style="{StaticResource panelBackground}">
<Grid Margin="15">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" FontSize="18" Text="Pending Guests" Margin="0,1,3,1" Foreground="SteelBlue" HorizontalAlignment="Left"/>
<TextBox Name="txtFirstNameFilter" Grid.Row="1" >
</TextBox>
<xcdg:DataGridControl x:Name="gridPendingGuests" Margin="5,0,5,1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
MinHeight="100"
MinWidth="200"
CellEditorDisplayConditions="None"
EditTriggers="None"
ItemScrollingBehavior="Immediate"
AutoCreateColumns="False"
SelectionMode="Single"
NavigationBehavior="RowOnly"
ItemsSource="{Binding Source={StaticResource firstNameDataSource}}">
<xcdg:DataGridControl.View>
<xcdg:TableView ShowRowSelectorPane="False"/>
</xcdg:DataGridControl.View>
<xcdg:DataGridControl.Columns>
<xcdg:Column x:Name="FirstName" FieldName="FirstName" Title="First Name" Width="150" />
</xcdg:DataGridControl.Columns>
<i:Interaction.Behaviors>
<utils:UpdateDataGridOnTextboxChange/>
</i:Interaction.Behaviors>
</xcdg:DataGridControl>
</Grid>
</DockPanel>
</ScrollViewer>
</Page>
In the datagrid, you have a collection of first names. This works perfectly. The display is good. As you can see, I added an Interactions.Behavior class which currently handles a filter with a hard coded value when the user clicks on the datagrid with their mouse. The filtering works fine. If there is a first name of "John", that record is removed from view, leaving all other records in place.
Here is that code:
using System.Windows.Interactivity;
using System.Windows;
using Xceed.Wpf.DataGrid;
using System;
namespace Some.Namespace.Behaviors
{
public class UpdateDataGridOnTextboxChange : Behavior<DataGridControl>
{
protected override void OnAttached()
{
AssociatedObject.MouseUp += AssociatedObjectOnMouseUp;
base.OnAttached();
}
protected override void OnDetaching()
{
AssociatedObject.MouseUp -= AssociatedObjectOnMouseUp;
base.OnDetaching();
}
private void AssociatedObjectOnMouseUp(object sender, RoutedEventArgs routedEventArgs)
{
var items = AssociatedObject.Items;
items.Filter = CollectionFilter;
}
private bool CollectionFilter(object item)
{
System.Data.DataRow dr = item as System.Data.DataRow;
//set the ItemArray as Guest
Guest guest = SetGuest(dr);
if (guest.FirstName.Equals("John"))
{
return false;
}
return true;
}
private Guest SetGuest(System.Data.DataRow dr)
{
Guest guest = new Guest();
guest.FirstName = dr.ItemArray[0].ToString();
return guest;
}
public class Guest
{
public string FirstName { get; set; }
}
}
}
This works as expected. Again, when the user clicks on the datagrid, the filter filters out the users with the First Name of "John".
What I WANT to have happen is for the user to be able to type a first name in the txtFirstNameFilter Textbox and the datagrid to then filter the records that contain the text in the first name, keeping them visible and the others without that first name to not be visible.
The way I can do it is with an attached property of the Textbox TextChanged property? That's a question, because I don't know how to do an attached property and then how to make sure that when that attached property actually changes, call the AssociatedObjectOnMouseUp method to run the filtering.
System.Windows.Interactivity.Behavior<T> inherits from DependencyObject. So give it a dependency property and bind that.
public class UpdateDataGridOnTextboxChange : Behavior<DataGrid>
{
#region FilterValue Property
public String FilterValue
{
get { return (String)GetValue(FilterValueProperty); }
set { SetValue(FilterValueProperty, value); }
}
public static readonly DependencyProperty FilterValueProperty =
DependencyProperty.Register(nameof(FilterValue), typeof(String), typeof(UpdateDataGridOnTextboxChange),
new FrameworkPropertyMetadata(null, FilterValue_PropertyChanged));
protected static void FilterValue_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as UpdateDataGridOnTextboxChange).OnFilterValueChanged(e.OldValue);
}
private void OnFilterValueChanged(object oldValue)
{
// Do whatever you do to update the filter
// I did a trace just for testing.
System.Diagnostics.Trace.WriteLine($"Filter value changed from '{oldValue}' to '{FilterValue}'");
}
#endregion FilterValue Property
/*****************************************
All your code here
*****************************************/
}
XAML:
<i:Interaction.Behaviors>
<utils:UpdateDataGridOnTextboxChange
FilterValue="{Binding Text, ElementName=txtFirstNameFilter}"
/>
</i:Interaction.Behaviors>
You should rename it, though. It's got nothing to do with text boxes. You could bind FilterValue to a viewmodel property, or the selected value in a ComboBox, or whatever.
Update
OP's having trouble with the binding only updating FilterValue when the text box loses focus. This isn't what I'm seeing, but I don't know what's different between the two.
There isn't any UpdateTargetTrigger property of Binding, but you can swap the source and the target when both are dependency properties of dependency objects. This works for me:
<TextBox
x:Name="txtFirstNameFilter"
Text="{Binding FilterValue, ElementName=DataGridFilterThing, UpdateSourceTrigger=PropertyChanged}"
/>
<!-- snip snip snip -->
<i:Interaction.Behaviors>
<local:UpdateDataGridOnTextboxChange
x:Name="DataGridFilterThing"
/>
</i:Interaction.Behaviors>

Get the number of items currently displayed in the listview WPF MVVM

I have a listview that can be filtered using a textbox:
<TextBox TextChanged="txtFilter_TextChanged" Name="FilterLv"/>
In the view code-behind I do the following:
CollectionView view = (CollectionView)CollectionViewSource.GetDefaultView(this.lv.ItemsSource);
view.Filter = UserFilter;
private bool UserFilter(object item)
{
if (String.IsNullOrEmpty(FilterLv.Text))
return true;
else
{
DataModel m = (item as DataModel);
bool result = (m.Name.IndexOf(Filter.Text, StringComparison.OrdinalIgnoreCase) >= 0 ||
//m.Surname.IndexOf(Filter.Text, StringComparison.OrdinalIgnoreCase) >= 0);
return result;
}
}
private void Filter_TextChanged(object sender, TextChangedEventArgs e)
{
CollectionViewSource.GetDefaultView(this.lv.ItemsSource).Refresh();
}
Now I have placed a label in the view and I would like this label to show the number of items currently displayed in the listview.
How can I do it? I have found things like this but I don't understand at all what is RowViewModelsCollectionView. In this link it is suggested to bind as below:
<Label Content="{Binding ModelView.RowViewModelsCollectionView.Count}"/>
Could anyone explain me or provide a very little and simple example on how to do it?
FINAL UPDATE:
View model:
public class TestViewModel
{
// lv is populated later in code
public ObservableCollection<DataModel> lv = new ObservableCollection<DataModel>();
public ObservableCollection<DataModel> LV
{
get
{
return this.lv;
}
private set
{
this.lv= value;
OnPropertyChanged("LV");
}
}
private CollectionView view;
public TestViewModel()
{
this.view = (CollectionView)CollectionViewSource.GetDefaultView(this.LV);
view.Filter = UserFilter;
}
private string textFilter;
public string TextFilter
{
get
{
return this.textFilter;
}
set
{
this.textFilter= value;
OnPropertyChanged("TextFilter");
if (String.IsNullOrEmpty(value))
this.view.Filter = null;
else
this.view.Filter = UserFilter;
}
}
private bool UserFilter(object item)
{
if (String.IsNullOrEmpty(this.TextFilter))
return true;
else
{
DataModel m = (item as DataModel);
bool result = (m.Name.IndexOf(this.TextFilter, StringComparison.OrdinalIgnoreCase) >= 0 ||
//m.Surname.IndexOf(this.TextFilter, StringComparison.OrdinalIgnoreCase) >= 0);
return result;
}
}
/// <summary>
/// Número de registros en la listview.
/// </summary>
public int NumberOfRecords
{
get
{
return this.view.Count;
}
}
}
View (xaml):
<!-- search textbox - filter -->
<TextBox TextChanged="txtFilter_TextChanged"
Text="{Binding TextFilter, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
<!-- label to show the number of records -->
<Label Content="{Binding NumberOfRecords}"/>
view code-behind (xaml.cs):
private void txtFilter_TextChanged(object sender, TextChangedEventArgs e)
{
CollectionViewSource.GetDefaultView((DataContext as TestViewModel).LV).Refresh();
}
It is filtering ok when I type in the search textbox and listview is updated correctly but the number of records is always 0.
What am i doing wrong?
ATTEMPT2:
Below another attempt not working. If I attach my listivew to the View declared in model view then no items are shown. If I attach listview to LV in model view then items are shown, and when I filter through my search textbox it filters ok, listview is updated but the number of rows shown in the listview always remains to 0.
Notes:
I am using NET 3.5 Visual Studio 2008.
I need to set View as writable in model view because I do not set it
in view model constructor, instead i set it in LoadData method in
view model. LoadData is called from view code-behind constructor.
View Model:
namespace MyTest.Example
{
public Class TestViewModel : INotifyPropertyChanged // Implementations not here to simplify the code here.
{
private ObservableCollection<DataModel> lv;
public ObservableCollection<DataModel> LV
{
get
{
return this.lv;
}
private set
{
this.lv = value;
OnPropertyChanged("LV");
}
}
public CollectionView View { get; set; }
public TestViewModel()
{
this.LV = new ObservableCollection<DataModel>();
// this.View = (CollectionView)CollectionViewSource.GetDefaultView(this.LV);
// this.View.Filter = UserFilter;
}
private string textFilter = string.Empty;
public string TextFilter
{
get
{
return this.textFilter ;
}
set
{
this.textFilter = value;
OnPropertyChanged("TextFilter");
this.View.Refresh();
}
}
private bool UserFilter(object item)
{
if (String.IsNullOrEmpty(this.TextFilter))
return true;
else
{
DataModel m = (item as DataModel);
bool result = (m.Name.IndexOf(this.TextFilter, StringComparison.OrdinalIgnoreCase) >= 0 ||
//m.Surname.IndexOf(this.TextFilter, StringComparison.OrdinalIgnoreCase) >= 0);
return result;
}
}
public void LoadData()
{
this.LV = LoadDataFromDB();
this.View = (CollectionView)CollectionViewSource.GetDefaultView(this.LV);
this.View.Filter = UserFilter;
}
} // End Class
} // End namespace
View code-behing (xaml.cs):
namespace MyTest.Example
{
public Class TestView
{
public TestView()
{
InitializeComponent();
(DataContext as TestViewModel).LoadData();
}
}
}
View (xaml):
xmlns:vm="clr-namespace:MyTest.Example"
<!-- search textbox - filter -->
<TextBox Text="{Binding Path=TextFilter, UpdateSourceTrigger=PropertyChanged}">
<!-- label to show the number of records -->
<Label Content="{Binding Path=View.Count}" ContentStringFormat="No. Results: {0}"/>
<ListView Grid.Row="1" Grid.Column="0" ItemsSource="{Binding Path=View}" SelectionMode="Extended" AlternationCount="2">
ATTEMPT 3:
Finally I have get it to work. Solution is the same as ATTEMPT2 but making below changes:
I have replaced this:
public CollectionView View { get; set; }
by this one:
private CollectionView view;
public CollectionView View {
get
{
return this.view;
}
private set
{
if (this.view == value)
{
return;
}
this.view = value;
OnPropertyChanged("View");
}
}
All the rest remains the same as in ATTEMPT2. In view View.Count and assigning View as ItemsSource to my listview now is working all perfectly.
You should use
<Label Content="{Binding ModelView.Count}"/>
instead of
<Label Content="{Binding ModelView.RowViewModelsCollectionView.Count}"/>
RowViewModelsCollectionView in the other question is the same as ModelView is in your case.
Edit
Count is a property from the CollectionView
For further information have a look at the MSDN
Edit 2
When you dont want to do it via XAML like in my example you have to implement INotifyPropertyChanged and raise this whenever the bound property is changed because otherwiese the UI won't get the change.
In your case: you have to call OnPropertyChanged("NumberOfRecords"); in your filter method. But it would be easier to do it via xaml like i Wrote earlier.
Here is a fully working example with the CollectionView in the view model, and the filter count automatically flowing to the bound control. It uses my mvvm library for the base ViewModel class to supply INotifyPropertyChanged, but you should easily be able to substitute your own system, I'm not doing anything special with it.
The full source code can be downloaded from here
XAML:
<Window
x:Class="FilterWithBindableCount.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:local="clr-namespace:FilterWithBindableCount"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="525"
Height="350"
d:DataContext="{d:DesignInstance local:MainWindowVm}"
mc:Ignorable="d">
<Grid Margin="4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Label
Grid.Row="0"
Grid.Column="0"
Margin="4">
Filter:
</Label>
<TextBox
Grid.Row="0"
Grid.Column="1"
Margin="4"
VerticalAlignment="Center"
Text="{Binding Path=FilterText, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="4"
Text="{Binding Path=PeopleView.Count, StringFormat={}Count: {0}}" />
<DataGrid
Grid.Row="3"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="4"
CanUserAddRows="False"
CanUserSortColumns="True"
ItemsSource="{Binding Path=PeopleView}" />
</Grid>
</Window>
View models:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
using AgentOctal.WpfLib;
namespace FilterWithBindableCount
{
class MainWindowVm : ViewModel
{
public MainWindowVm()
{
People = new ObservableCollection<PersonVm>();
PeopleView = (CollectionView) CollectionViewSource.GetDefaultView(People);
PeopleView.Filter = obj =>
{
var person = (PersonVm)obj;
return person.FirstName.ToUpper().Contains(FilterText.ToUpper() ) || person.LastName.ToUpper().Contains(FilterText.ToUpper());
};
People.Add(new PersonVm() { FirstName = "Bradley", LastName = "Uffner" });
People.Add(new PersonVm() { FirstName = "Fred", LastName = "Flintstone" });
People.Add(new PersonVm() { FirstName = "Arnold", LastName = "Rimmer" });
People.Add(new PersonVm() { FirstName = "Jean-Luc", LastName = "Picard" });
People.Add(new PersonVm() { FirstName = "Poppa", LastName = "Smurf" });
}
public ObservableCollection<PersonVm> People { get; }
public CollectionView PeopleView { get; }
private string _filterText = "";
public string FilterText
{
get => _filterText;
set
{
if (SetValue(ref _filterText, value))
{
PeopleView.Refresh();
}
}
}
}
class PersonVm:ViewModel
{
private string _firstName;
public string FirstName
{
get {return _firstName;}
set {SetValue(ref _firstName, value);}
}
private string _lastName;
public string LastName
{
get {return _lastName;}
set {SetValue(ref _lastName, value);}
}
}
}
This is actually significantly easier when properly following MVVM. The CollectionView is either declared in the XAML, or as a property in the viewmodel. This allows you to bind directly to CollectionView.Count.
Here is an example of how to place the CollectionViewSource in XAML from one of my apps:
<UserControl
x:Class="ChronoPall.App.TimeEntryList.TimeEntryListView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:app="clr-namespace:ChronoPall.App"
xmlns:componentModel="clr-namespace:System.ComponentModel;assembly=WindowsBase"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ChronoPall.App.TimeEntryList"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DataContext="{d:DesignInstance local:TimeEntryListViewVm}"
d:DesignHeight="300"
d:DesignWidth="300"
mc:Ignorable="d">
<UserControl.Resources>
<CollectionViewSource x:Key="TimeEntriesSource" Source="{Binding Path=TimeEntries}">
<CollectionViewSource.SortDescriptions>
<componentModel:SortDescription Direction="Descending" PropertyName="StartTime.Date" />
<componentModel:SortDescription Direction="Ascending" PropertyName="StartTime" />
</CollectionViewSource.SortDescriptions>
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="EntryDate" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</UserControl.Resources>
<Grid IsSharedSizeScope="True">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding Source={StaticResource TimeEntriesSource}}">
<ItemsControl.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate DataType="{x:Type CollectionViewGroup}">
<local:TimeEntryListDayGroup />
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ItemsControl.GroupStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:TimeEntryListItem />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
</UserControl>
It doesn't actually bind to Count, but it could easily do that with:
<TextBlock Text="{Binding Path=Count, Source={StaticResource TimeEntriesSource}}/>
To do it in the viewmodel, you would just create a readonly property of ICollectionView, and set it equal to CollectionViewSource.GetDefaultView(SomeObservableCollection‌​), then bind to that.

How to bind two elements to single source in WPF

My scenario is that there are two controls. One in which you set up minutes and second in which you specify seconds.
Both of them should be bound to single property in view model. This property is of type string. This string is in format [hh:mm:ss]. So changing value in "minutes" control should change 'mm' portion of the string and changing the value in "seconds" control should change the 'ss' portion of the string.
Thanks in advance
Here is a 3-property ViewModel working solution if you are using TimeSpan and its range is between 0 and 59h 59s. I have not fully tested and conditions/validation will change based on requirements. I used TimeSpan.TotalSeconds because that's the resolution we needed; meaning, when setting the TimeSpan to a new value, we would just set the total number of seconds through the public property. An alternative could be to have 2 TimeSpan properties in your ViewModel, then when setting the public property, you could call _item.TotalSeconds = VMMinutes.TotalSeconds + VMSeconds.TotalSeconds.TotalSeconds. Basically you have many design options here.
MainWindow.xaml:
<Grid>
<StackPanel>
<Border Height="60" BorderBrush="Black" BorderThickness="1">
<StackPanel Orientation="Horizontal">
<Label Content="Minutes"/>
<TextBox Text="{Binding Minutes}" />
<Label Content="Seconds"/>
<TextBox Text="{Binding Seconds}" />
</StackPanel>
</Border>
<Border Height="60" BorderBrush="Black" BorderThickness="1">
<StackPanel Orientation="Horizontal">
<Label Content="Total Seconds"/>
<TextBox Text="{Binding TotalSeconds}" />
</StackPanel>
</Border>
</StackPanel>
</Grid>
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ItemViewModel(new Item(new TimeSpan(0, 3, 59)));
}
}
ItemViewModel.cs:
public class ItemViewModel : INotifyPropertyChanged
{
private readonly Item _item;
public event PropertyChangedEventHandler PropertyChanged;
public ItemViewModel(Item item)
{
_item = item;
}
public string TotalSeconds
{
get
{
return _item.TotalSeconds.ToString();
}
set
{
double newTotSecs;
if(!string.IsNullOrEmpty(value))
{
if(double.TryParse(value, out newTotSecs))
{
_item.TotalSeconds = newTotSecs;
NotifyPropertyChanged();
NotifyPropertyChanged("Minutes");
NotifyPropertyChanged("Seconds");
}
}
}
}
public string Seconds
{
get
{
return (_item.TotalSeconds % 60).ToString();
}
set
{
int newVal;
if(!string.IsNullOrEmpty(value))
{
if(int.TryParse(value, out newVal))
{
if(newVal >= 0 && newVal <= 59)
{
int totMinSec;
if(int.TryParse(Minutes, out totMinSec))
{
_item.TotalSeconds = (totMinSec * 60) + newVal;
NotifyPropertyChanged();
NotifyPropertyChanged("TotalSeconds");
}
}
}
}
}
}
public string Minutes
{
get
{
return ((int)(_item.TotalSeconds / 60)).ToString();
}
set
{
int newVal;
if(!string.IsNullOrEmpty(value))
{
if(int.TryParse(value, out newVal))
{
if(newVal >= 0 && newVal <= 59)
{
int totSec;
if(int.TryParse(Seconds, out totSec))
{
_item.TotalSeconds = totSec + (newVal * 60);
NotifyPropertyChanged();
NotifyPropertyChanged("TotalSeconds");
}
}
}
}
}
}
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Item.cs:
public class Item
{
private TimeSpan _time;
public double TotalSeconds
{
get
{
return _time.TotalSeconds;
}
set
{
if(value >= 0)
{
_time = new TimeSpan(0, 0, (int)value);
}
}
}
public Item(TimeSpan time)
{
_time = time;
}
}
Note: Your other option is to use a Converter, which I haven't provided a solution for. I think it could end up being cleaner in the long run since all you really need to pass to back and forth is the converter is total number of seconds.
I would use NETScape's approach above, but encapsulate it in a user control. The user control XAML would be something like:
<UserControl>
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Minutes" Grid.Row="0" Grid.Column="0"/>
<TextBox Text="{Binding InternalMinutes}" Grid.Row="0" Grid.Column="1"/>
<TextBlock Text="Seconds" Grid.Row="1" Grid.Column="0"/>
<TextBox Text="{Binding InternalSeconds}" Grid.Row="1" Grid.Column="1"/>
</Grid>
</UserControl>
Then in the code-behind, you would have a Dependency Property for the actual DateTime object, and properties to bind against (you could use a view model for this, or just go off of TextChanged. When its all View logic, its ok!).
An example property would be:
public int InternalSeconds
{
get { return ExternalTime.Seconds; }
set
{
ExternalTime.Seconds = value;
NotifyPropertyChanged();
}
}
Again, there are multiple approaches here, you could use a converter in order to use an intermediate object. ExternalTime is the DP here, make sure to handle its Changed event if you expect the value to change outside of this control.

How to Hide/enable a Column in userControl when calling that control in other window?

I have a User control. It has Some textboxes. I need to hide a single column in that control and if require I need to set as visible. It's like, setting visibility property to a textbox visible/hidden/collapsed. Same thing I need to do in a Column for a user control.
Here is my code.
UserControl Xaml:
<UserControl x:Class="UserControls.UserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Grid.RowDefinitions >
<RowDefinition Height="45"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="15*" />
<ColumnDefinition Width="10*" />
<ColumnDefinition Width="10*" />
</Grid.ColumnDefinitions>
<TextBox Name="txt1"
Text=""
Grid.Row="0" Grid.Column="0"/>
<TextBox Name="txt1"
Text=""
Grid.Row="0" Grid.Column="1"/>
//....some othr controls..
</Grid>
</UserControl>
Window1.cs:
public partial class Window1
{
public Window1()
{
InitializeComponent();
var uc = new UserControl();
grid1.RowDefinitions.Add(new RowDefinition());
Grid.SetRow(uc, grid1.RowDefinitions.Count - 1);
grid1.Children.Add(uc);
}
}
I need to hide the Column 1. How might I conceal this column and if require I need to Enable this column too. Any offer assistance??
Column cannot be hidden by itself. Nevertheless you can wrap elements in column to one panel and then set its visibility to hidden/collapsed. You can also select all UIelements from column and subsequently set their visibility
var elements = Grid.Children.OfType<FrameworkElement>().Where(x => Grid.GetColumn(x) == ColumnNumber).ToList();
elements.ForEach(x => x.Visibility = System.Windows.Visibility.Collapsed);
EDIT
I created Attached Properties for you problem. I have simple grid panel with 2 elements inside
<Grid local:MyGrid.IsHidden="True" local:MyGrid.ColumnNumber="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="1" Grid.Column="0"/>
<TextBlock Text="2" Grid.Column="1" Name="tekst"/>
</Grid>
class MyGrid looks as follows:
public class MyGrid
{
public static void SetIsHidden(DependencyObject obj, bool val)
{
obj.SetValue(IsHiddenProperty, val);
}
public static bool GetIsHidden(DependencyObject obj)
{
return (bool)obj.GetValue(IsHiddenProperty);
}
public static void SetColumnNumber(DependencyObject obj, int val)
{
obj.SetValue(ColumnNumberProperty, val);
}
public static int GetColumnNumber(DependencyObject obj)
{
return (int)obj.GetValue(ColumnNumberProperty);
}
public static readonly DependencyProperty IsHiddenProperty = DependencyProperty.RegisterAttached("IsHidden", typeof(bool), typeof(MyGrid));
public static readonly DependencyProperty ColumnNumberProperty = DependencyProperty.RegisterAttached("ColumnNumber", typeof(int), typeof(MyGrid),
new FrameworkPropertyMetadata(-1, new PropertyChangedCallback((x, y) =>
{
if (x is Grid && GetIsHidden(x))
((Grid)x).Loaded += MyGrid_Loaded;
})));
static void MyGrid_Loaded(object sender, RoutedEventArgs e)
{
if (GetColumnNumber((DependencyObject)sender) >= 0 && GetColumnNumber((DependencyObject)sender) <= ((Grid)sender).ColumnDefinitions.Count - 1)
{
var elements = ((Grid)sender).Children.OfType<FrameworkElement>().Where(z => Grid.GetColumn(z) == GetColumnNumber((DependencyObject)sender)).ToList();
elements.ForEach(s => s.Visibility = System.Windows.Visibility.Collapsed);
}
}
}
Now if you set local:MyGrid.IsHidden to True and insert valid ColumnNumber local:MyGrid.ColumnNumber all UI elements will be hidden.
Standard view
If you set
local:MyGrid.IsHidden="True" local:MyGrid.ColumnNumber="0"
For settings
local:MyGrid.IsHidden="False" local:MyGrid.ColumnNumber="0"
everyting stays normal
As you can see from the ColumnDefinition Class page on MSDN, there is no Visibility property. Therefore, it is not possible to set the Visibililty of a Grid.Column. The customary way to do this would be to set the Visibility on each of the controls in that column.
The standard way to set the Visibility on one or more controls from other controls is to provide one or more bool properties:
public bool IsVisible { get; set; } // Implement INotifyPropertychanged interface here
Then we can data bind that property to the Control.Visibillity property of the relevant controls:
<TextBox Name="txt1" Text="" Grid.Row="0" Grid.Column="0" Visibillity="{
Binding IsVisible, Converter={StaticResource BooleanToVisibilityConverter}}" />
You can use the same Binding on each of the controls in the column, so that they can all be hidden at once:
// Hide column controls
IsVisible = false;
// Show column controls
IsVisible = true;
Finally, if you create a DependencyProperty for the IsVisible property, then you would be able to data bind to it from outside of your UserControl:
<Local:YourUserControl IsVisible="{Binding IsVisibleInMainWindowDataContext}" />
Then from object set as MainWindow.DataContext:
// Hide column controls
IsVisibleInMainWindowDataContext = false;
// Show column controls
IsVisibleInMainWindowDataContext = true;

How to set Grid Row with RowDefinition name?

I'm organizing my grid with RowDefinitions and ColumnDefinition, but forever when I want add a new RowDefinition in before actual any RowDefinition, I need reorganize Grid.Row of all controls
I saw RowDefinition and ColumnDefinition has a Name property, so I think is possible define Grid.Row with RowDefinition name or not? If is possible, How do
<Grid>
<Grid.RowDefinitions>
<RowDefinition Name="RowDictTitle" Height="27"/>
<RowDefinition Name="RowSearchWord" Height="27"/>
<RowDefinition/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<!--Row 1-->
<TextBlock Text="Word:" VerticalAlignment="Center" Margin="10,0,0,0" Grid.Row="1"/>
<TextBox Name="Search" Grid.ColumnSpan="2" Margin="50,2,10,2"/>
<!--Row 2-->
<ListBox Name="Words" Grid.Row="2" Margin="10"/>
</Grid>
I want make below
<TextBlock Text="Word" Grid.Row="RowSearchWord"/>
Disclaimer: This answer is kind of a self-advertisement within the constraints alluded to by this meta post. It advertises a free open source project that I (at the time of writing this) do not earn any money with. The only gain is the knowledge that my time for writing the described control was not wasted if it helps some future visitors of this SO question.
I had exactly the same thoughts. That is why, not too long ago, I wrote a custom grid class that uses named columns and rows.
I put it on Codeplex under the MIT license: Name-Based Grid project
With that control, you can rewrite your Xaml source code as follows:
<nbg:NameBasedGrid>
<nbg:NameBasedGrid.RowDefinitions>
<nbg:ColumnOrRow Name="RowDictTitle" Height="27"/>
<nbg:ColumnOrRow Name="RowSearchWord" Height="27"/>
<nbg:ColumnOrRow Name="List"/>
<nbg:ColumnOrRow Height="50"/>
</nbg:NameBasedGrid.RowDefinitions>
<nbg:NameBasedGrid.ColumnDefinitions>
<nbg:ColumnOrRow Width="1*" Name="Left"/>
<nbg:ColumnOrRow Width="2*" Name="Right"/>
</nbg:NameBasedGrid.ColumnDefinitions>
<!--Row 1-->
<TextBlock Text="Word:" VerticalAlignment="Center" Margin="10,0,0,0" nbg:NameBasedGrid.Column="Left" nbg:NameBasedGrid.Row="RowSearchWord"/>
<TextBox Name="Search" nbg:NameBasedGrid.Column="Left" nbg:NameBasedGrid.Row="RowDictTitle" nbg:NameBasedGrid.ExtendToColumn="Right" Margin="50,2,10,2"/>
<!--Row 2-->
<ListBox Name="Words" nbg:NameBasedGrid.Column="Left" nbg:NameBasedGrid.Row="List" Margin="10"/>
</nbg:NameBasedGrid>
Advantage: You will be able to reference columns and rows (including column and row spans!) by name - no more counting of columns or rows, no more updating column or row spans when the layout changes.
Disadvantage: You will need to explicitly state names for all columns and rows, as numerical references are not supported at all in NameBasedGrid.
Nice idea but since the Grid.Row attached property is an integer this is not possible.
See http://msdn.microsoft.com/en-us/library/system.windows.controls.grid.row.aspx
However, it may possible to create a helper that takes the name of the grid row, finds the row object and returns its row index.
I was looking for the same thing. Could not find exacly what I was looking for so i came up with my own solution using attached properties.
I created a specialized grid with attached properties for RowName and ColumnName.
(In this example i only implemented RowName)
using System.Windows;
using System.Windows.Controls;
namespace GridNamedRows.CustomControl
{
public class MyGrid: Grid
{
public static readonly DependencyProperty RowNameProperty =
DependencyProperty.RegisterAttached(
"RowName",
typeof(string),
typeof(MyGrid),
new FrameworkPropertyMetadata(
"",
FrameworkPropertyMetadataOptions.AffectsParentArrange,
new PropertyChangedCallback(RowNameChanged)),
new ValidateValueCallback(IsStringNotNull));
private static bool IsStringNotNull(object value)
{
return (value as string) != null;
}
private static void RowNameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue == null)
{
return;
}
if (!(d is UIElement)) return;
Grid parent = ((FrameworkElement)d).Parent as Grid;
if (parent == null) return;
//Find rowname
for (int i = 0; i < parent.RowDefinitions.Count; i++)
{
if (parent.RowDefinitions[i].Name == e.NewValue.ToString())
{
Grid.SetRow((UIElement)d, i);
break;
}
}
}
public static string GetRowName(DependencyObject target)
{
return (string)target.GetValue(RowNameProperty);
}
public static void SetRowName(DependencyObject target, string value)
{
target.SetValue(RowNameProperty, value);
}
}
}
It can be used in xaml like this.
<Window xmlns:CustomControl="clr-namespace:GridNamedRows.CustomControl" x:Class="GridNamedRows.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<CustomControl:MyGrid>
<Grid.RowDefinitions>
<RowDefinition Name="firstRow"/>
<RowDefinition Name="secondRow"/>
<RowDefinition Name="thirdRow"/>
</Grid.RowDefinitions>
<TextBox Text="one" CustomControl:MyGrid.RowName="secondRow"/>
<TextBox Text="two" Grid.Row="2"/>
<TextBox Text="three" CustomControl:MyGrid.RowName="firstRow"/>
</CustomControl:MyGrid>
</Window>
It does not display correctly in the designer but works in runtime.
Along the lines of the other answers I came up with this attached property solution that does not require using a custom Grid.
The code is largely redundant (for row & column) and can be used like this:
<Grid>
<Grid.RowDefinitions>
<RowDefinition x:Name="ThisRow"/>
<RowDefinition x:Name="ThatRow"/>
<RowDefinition x:Name="AnotherRow"/>
</Grid.RowDefinitions>
<TextBlock helpers:GridHelper.RowName="ThisRow" Text="..."/>
<TextBlock helpers:GridHelper.RowName="AnotherRow" Text="..."/>
<TextBlock helpers:GridHelper.RowName="ThatRow" Text="..."/>
</Grid>
GridHelper.cs:
public class GridHelper
{
public static string GetRowName(DependencyObject obj)
{
return (string)obj.GetValue(RowNameProperty);
}
public static void SetRowName(DependencyObject obj, string value)
{
obj.SetValue(RowNameProperty, value);
}
public static readonly DependencyProperty RowNameProperty =
DependencyProperty.RegisterAttached("RowName", typeof(string), typeof(GridHelper), new FrameworkPropertyMetadata(string.Empty, GridHelper.OnRowNamePropertyChanged));
public static void OnRowNamePropertyChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var name = e.NewValue?.ToString();
if (string.IsNullOrEmpty(name)) return;
if (!(sender is FrameworkElement fe)) return;
if (!(fe.Parent is Grid grid)) return;
for (int i = 0; i < grid.RowDefinitions.Count; i++)
{
var rd = grid.RowDefinitions[i];
if (rd.Name.Equals(name))
{
Grid.SetRow(fe, i);
return;
}
}
throw new ArgumentException("Invalid RowName: " + name);
}
public static string GetColumnName(DependencyObject obj)
{
return (string)obj.GetValue(ColumnNameProperty);
}
public static void SetColumnName(DependencyObject obj, string value)
{
obj.SetValue(ColumnNameProperty, value);
}
public static readonly DependencyProperty ColumnNameProperty =
DependencyProperty.RegisterAttached("ColumnName", typeof(string), typeof(GridHelper), new FrameworkPropertyMetadata(string.Empty, GridHelper.OnColumnNamePropertyChanged));
public static void OnColumnNamePropertyChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var name = e.NewValue?.ToString();
if (string.IsNullOrEmpty(name)) return;
if (!(sender is FrameworkElement fe)) return;
if (!(fe.Parent is Grid grid)) return;
for (int i = 0; i < grid.ColumnDefinitions.Count; i++)
{
var cd = grid.ColumnDefinitions[i];
if (cd.Name.Equals(name))
{
Grid.SetColumn(fe, i);
return;
}
}
throw new ArgumentException("Invalid ColumnName: " + name);
}
}
Note: This also may not work in the designer - I've never tried using it...

Resources