How to bind TextBoxes to individual elements in a Collection - wpf

I have a class with an ObservableCollection called List and I am trying to bind to textboxes individually. I have been trying:
<TextBox Text="{Binding Source=List[0], Path=Value}" />
<TextBox Text="{Binding Source=List[1], Path=Value}"/>
The StringObject class is just:
class StringObject
{
public string Value { get; set; }
}
Can someone advise?

If this is for a WPF app.
Given this code behind:
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
this.DataContext = new ListCon();
}
}
public class ListCon
{
public List<StringObject> List
{
get
{
var list = new List<StringObject>();
list.Add(new StringObject() { Value = "Hello World" });
return list;
}
}
}
public class StringObject
{
public string Value { get; set; }
}
The binding would look like this:
<TextBox Text="{Binding List[0].Value}" />

Related

Using RegularExpression as attribute in .NET

I am testing use of regular expression as attribute in my application, but it is simply not working.
public partial class MainWindow : Window
{
[Required]
[RegularExpression(#"^[\d]+")]
public string number { get; set; }
public MainWindow()
{
InitializeComponent();
number = "sometext";
}
}
No error is being thrown and number accepts anything without caring for RegularExpression attribute.
How can I make number only to accept what is mentioned in regex? Usually I do validate in setter, but have learnt recently about attribute and wish to use it.
Thanks.
Your binding source must implement the IDataErrorInfo interface. Then you can set the ValidatesOnDataErrors and NotifyOnValidationError propeties on the binding.
See a simplified example below.
A base class to handle property changes and validation.
internal abstract class ValidatedObservableBase : INotifyPropertyChanged, IDataErrorInfo
{
public event PropertyChangedEventHandler PropertyChanged;
public string this[string columnName]
{
get
{
var results = new List<ValidationResult>();
var valid = Validator.TryValidateProperty(GetType().GetProperty(columnName)?.GetValue(this), new ValidationContext(this) { MemberName = columnName }, results);
return valid ? null : results[0].ErrorMessage;
}
}
public string Error
{
get => null;
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The model, derived from the above base class.
internal class Model : ValidatedObservableBase
{
private string number;
[Required(ErrorMessage = "Required error")]
[RegularExpression(#"^[\d]+", ErrorMessage = "Regex error")]
public string Number
{
get => number;
set
{
number = value;
OnPropertyChanged();
}
}
}
A simple view model to set as the window's DataContext.
internal class ViewModel
{
public Model Model { get; set; } = new Model();
}
Lastly, the window.
<Window
...
xmlns:local="clr-namespace:Demo"
mc:Ignorable="d">
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<StackPanel>
<TextBox
x:Name="TB"
Margin="24,24,24,0"
VerticalAlignment="Top"
Text="{Binding Path=Model.Number, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, NotifyOnValidationError=True}" />
<TextBlock
Margin="24,4,24,24"
Foreground="Red"
Text="{Binding ElementName=TB, Path=(Validation.Errors)[0].ErrorContent}" />
</StackPanel>
</Window>
Thanks for comments. I modified code with some information on this site. myTextbox is bind with number and am using validation attribute. But still this is accepting everything that I write in my textbox.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
myTextBox.DataContext = this;
}
[Required]
[AcceptNumberAttribute]
public string number { get; set; }
}
public sealed class AcceptNumberAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
return new RegularExpressionAttribute(#"^[\d]$").IsValid(Convert.ToString(value).Trim());
}
}

WpfCombo box itemsource binding call back

I have a combobox where it binded to view model list property. This list property then calls to the async function in data layer.
I wanted to set the selected index property of the combobox to "zero" I.e selected index=0;
Real scenario is data is perfectly loading but even after I set selected index property it is not applying since async call.
Please let me know any callback method after property bonded.
I have made a sample application
ViewModel
class MainWindowViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<string> _source;
private object _lock = new object();
public IEnumerable<string> Source
{
get { return _source; }
}
public String SelectedItem { get; set; }
public int SelectedIndex { get; set; }
public MainWindowViewModel()
{
_source = new ObservableCollection<string>();
BindingOperations.EnableCollectionSynchronization(_source, _lock);
Task.Factory.StartNew(() => PopulateSourceAsunc());
}
private void PopulateSourceAsunc()
{
for (int i = 0; i < 10; i++)
{
_source.Add("Test " + i.ToString());
Thread.Sleep(1000);
}
//SelectedItem = _source[6];
SelectedIndex = 5;
OnPropertyChanged("SelectedIndex");
}
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Xaml
<StackPanel>
<ComboBox ItemsSource="{Binding Source}"
SelectedItem="{Binding SelectedItem}"
SelectedIndex="{Binding SelectedIndex}">
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel/>
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
</ComboBox>
</StackPanel>
Code behind
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel();
}
BindingOperations class is in the namespace of System.Windows.Data

WPF Combobox initial dictionary binding value not showing

I have a wpf combobox bound to a property LogicalP of a class SInstance. The ItemSource for the combobox is a dictionary that contains items of type LogicalP.
If I set LogicalP in SInstance to an initial state, the combobox text field shows empty. If I select the pulldown all my dictionary values are there. When I change the selection LogicalP in SInstance gets updated correctly. If I change Sinstance in C# the appropriate combobox value doesn't reflect the updated LogicalP from the pulldown.
I've set the binding mode to twoway with no luck. Any thoughts?
My Xaml:
<UserControl.Resources>
<ObjectDataProvider x:Key="PList"
ObjectType="{x:Type src:MainWindow}"
MethodName="GetLogPList"/>
</UserControl.Resources>
<DataTemplate DataType="{x:Type src:SInstance}">
<Grid>
<ComboBox ItemsSource="{Binding Source={StaticResource PList}}"
DisplayMemberPath ="Value.Name"
SelectedValuePath="Value"
SelectedValue="{Binding Path=LogicalP,Mode=TwoWay}">
</ComboBox>
</Grid>
</DataTemplate>
My C#:
public Dictionary<string, LogicalPType> LogPList { get; private set; }
public Dictionary<string, LogicalPType> GetLogPList()
{
return LogPList;
}
public class LogicalPType
{
public string Name { get; set; }
public string C { get; set; }
public string M { get; set; }
}
public class SInstance : INotifyPropertyChanged
{
private LogicalPType _LogicalP;
public string Name { get; set; }
public LogicalPType LogicalP
{
get { return _LogicalP; }
set
{
if (_LogicalP != value)
{
_LogicalP = value;
NotifyPropertyChanged("LogicalP");
}
}
}
#region INotifyPropertyChanged Members
#endregion
}
They are not looking at the same source.
You need to have SInstance supply both the LogPList and LogicalP.
_LogicalP is not connected to LogPList
If you want to different objects to compare to equal then you need to override Equals.
Here's my working solution. By moving the dictionary retrieval GetLogPList to the same class as that supplies the data (as suggested by Blam) I was able to get the binding to work both ways. I changed binding to a list rather than a dictionary to simplify the combobox
Here's the updated Xaml showing the new ItemsSource binding and removal of the SelectedValuePath:
<DataTemplate DataType="{x:Type src:SInstance}">
<Grid>
<ComboBox ItemsSource="{Binding GetLogPList}"
DisplayMemberPath ="Name"
SelectedValue="{Binding Path=LogicalP,Mode=TwoWay}">
</ComboBox>
</Grid>
</DataTemplate>
I then changed the dictionary LogPList to static so that it would be accessible to the class SInstance:
public static Dictionary<string, LogicalPType> LogPList { get; private set; }
Finally, I moved GetLogPList to the class SInstance as a property. Note again it's returning a list as opposed to a dictionary to make the Xaml a little simpler:
public class SInstance : INotifyPropertyChanged
{
public List<LogicalPType> GetLogPList
{
get { return MainWindow.LogPList.Values.ToList(); }
set { }
}
private LogicalPType _LogicalP;
public string Name { get; set; }
public LogicalPType LogicalP
{
get { return _LogicalP; }
set
{
if (_LogicalP != value)
{
_LogicalP = value;
NotifyPropertyChanged("LogicalP");
}
}
}
#region INotifyPropertyChanged Members
#endregion
}

Why won't MEF Lazy load work in the ViewModel?

I'm trying to get Lazy to work for a collection in my ViewModel that I'm binding to. The collection loads through MEF fine, but never gets displayed in the bound UI.
Here's the UI:
<Window x:Class="TestWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<ItemsControl ItemsSource="{Binding MyList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Label Content="{Binding ItemTitle}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<StackPanel>
</Window>
The code-behind class:
public partial class TestWindow : Window
{
public TestWindow()
{
InitializeComponent();
this.DataContext = new TestVM();
}
}
The ViewModel:
public class TestVM : INotifyPropertyChanged, IPartImportsSatisfiedNotification
{
public TestVM()
{
//I'm using a static class to initiate the import
CompositionInitializer.SatisfyImports(this);
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
[ImportMany(typeof(MyItemBase))]
public Lazy<MyItemBase>[] MyList { get; set; }
public void OnImportsSatisfied()
{
this.PropertyChanged(this, new PropertyChangedEventArgs("MyList"));
}
}
The base class for the items, and some inherited test classes:
[InheritedExport]
public class MyItemBase
{
public MyItemBase()
{
}
public string ItemTitle{ get; set; }
}
public class MyItem1: MyItemBase
{
public MyItem1()
{
this.ItemTitle = "Item 1";
}
}
public class MyItem2: MyItemBase
{
public MyItem2()
{
this.ItemTitle = "Item 2";
}
}
This works IF I just remove the Lazy loading. However, I'll need to apply some export attributes later, which means going to Lazy.
the problem is that you want bind to a list of MyItembase object, but your actual binding is to a lazy arrray of MyItembase objects.(as long as you never call .Value for your lazy item nothing will happen)
i my projects i use a private lazy collection for mef and a normal ObservableCollection for wpf. btw i would prefer Constructor injection for your Mef import
public class TestVM : INotifyPropertyChanged, IPartImportsSatisfiedNotification
{
public TestVM()
{
//I'm using a static class to initiate the import
CompositionInitializer.SatisfyImports(this);
this.MyList = new ObservableCollection();
foreach(var lazyitem in _mefList)
{
this.MyList.Add(lazyitem.Value);
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public ObservbableCollection<MyItemBase> MyList{ get; set; }
[ImportMany(typeof(MyItemBase))]
private IEnumarable<Lazy<MyItemBase>> _mefList { get; set; }
public void OnImportsSatisfied()
{
//this.PropertyChanged(this, new PropertyChangedEventArgs("MyList"));
}
}

WPF DataGrid binding not working

I cannot get DataGrid binding to work in the example bellow.
Any clues on what's going on ?
namespace WPFTestApplication
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public class Person
{
public int age { get; set; }
public String Name { get; set; }
public Person(int age, String Name)
{
this.age = age;
this.Name = Name;
}
}
public class MegaObject
{
public IList<Person> persons { get; set; }
public MegaObject()
{
persons = new List<Person>();
persons.Add(new Person(11, "A"));
persons.Add(new Person(12, "B"));
persons.Add(new Person(13, "C"));
}
}
public Window1()
{
InitializeComponent();
MegaObject myobject= new MegaObject();
DataContext = myobject;
}
}
}
<Grid>
<my:DataGrid
Name="dataGrid"
AutoGenerateColumns="False"
ItemsSource="{Binding Source=persons}"
>
<my:DataGrid.Columns>
<my:DataGridTextColumn Binding="{Binding Path=age, Mode=TwoWay}" >
</my:DataGridTextColumn>
<my:DataGridTextColumn Binding="{Binding Path=Name, Mode=TwoWay}" >
</my:DataGridTextColumn>
</my:DataGrid.Columns>
</my:DataGrid>
</Grid>
Regards,
MadSeb
The ItemsSource binding needs to have Path set, not Source, to persons. Simply putting it as {Binding persons} would do the trick (Path is the default property in markup) or explicitly {Binding Path=persons}. The DataContext is always inherited.

Resources