How to bind a TextBox to plain string collection - wpf

As a part of large data model I need to display/edit a string collection defined like ObservableCollection<String>. In prototype app we use a list view to display entire of collection and a text box to edit selected element. The text box should be bound to the current element of the collection. Because GUI is subject to change I can't bind directly using <TextBox Text="{Binding SelectedItem,ElementName=listView}" />.
I tried to use
<TextBox Text="{Binding Path=/, UpdateSourceTrigger=PropertyChanged}"/>
but it works only in one direction, changing listview current item causes updating a text box but not otherwise.
How can I bind a text box directly to sting instance of current element in string collection?

Ok, so here's your ListView. I'm going to add a name to it so I can reference it elsewhere in the XAML:
<ListView
x:Name=stringList
ItemsSource="{Binding}"
SelectionMode="Single"
IsSynchronizedWithCurrentItem="True">
<ListView.View>
<GridView>
<GridViewColumn
Header="Data Item"
Width="80"
DisplayMemberBinding="{Binding}"/>
</GridView>
</ListView.View>
</ListView>
Now in your TextBox over on the right you can bind directly to the ListView:
<TextBox Text="{Binding SelectedItem,ElementName=stringList}" />
Since your ListView is bound directly to a list of strings, SelectedItem will be the string the currently-selected ListViewItem points to.
Update
Since you're not allowed to use ElementBinding, your best bet is to introduce a ViewModel class to sit between your list and your window. Define it like this:
public class StringListViewModel : INotifyPropertyChanged
{
// you'll have to implement INotifyPropertyChanged - I won't
// do that here - do a quick search to learn how it works.
public ObservableCollection<String> List { get; set; }
private object _si;
public object SelectedItem
{
get { return _si; }
set
{
_si = value;
OnPropertyChanged("SelectedItem");
}
}
}
Now set your window's DataContext to an instance of your ViewModel class instead of pointing it directly to the string list. Bind your ListView's ItemsSource and SelectedItem to it like this:
<ListView ItemsSource="{Binding List}" SelectedItem="{Binding SelectedItem}" ... />
Now bind your TextBox to the SelectedItem of your ViewModel:
<TextBox Text="{Binding SelectedItem}" />
Now your list sets the SelectedItem on the ViewModel whenever it changes, and thus your TextBox reflects that value. Hope that makes sense.

Related

How to bind a GridViewColumn to a Property inside it's Collection Item in WPF

I created an attached property for a GridView's GridViewColumn to hide/show the whole column.
<GridViewColumn att:Visibility.IsVisible="{Binding ...}">
This works as long the property the attached property binds to exists in my main view model (the datacontext of my gridView).
In one case, the visibility of the column depends on a property of my list item (an item inside the collection that is the itemsource of my gridview).
How do I bind here?
I can access the item properties inside the cell template of the gridviewcolumn but not in the gridviewcolumn itself.
What I'm trying to achieve is, that I can bind the attached property to a property of my list item.
Is there a way to do this?
Edit:
Let's say I have a class:
public class Item : INotifyPropertyChanged
{
private string m_Text;
public string Text
{
get
{
return m_Text;
}
set
{
m_Text= value;
RaisePropertyChanged("Text");
}
}
private bool m_IsVisible;
public bool IsVisible
{
get
{
return m_IsVisible;
}
set
{
m_IsVisible = value;
RaisePropertyChanged("IsVisible");
}
}
}
Now I have an ObservableCollection of this class as ItemsSource of my ListView/GridView.
public ObservableCollection<Item> Items;
In my xaml:
<ListView ItemsSource={Binding Items}>
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn att:Visibility.IsVisible="{Binding /*What goes here? Should bind to IsVisible of my Collection Item*/}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text={Binding Text"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView/>
The visibility of my gridviewcolumn should now depend on the value of "IsVisible" of my collection item. Is that event possible?
You can bind your attached property to the IsVisible property of your collection. I noticed that your xaml syntax is incorrect cause compilation errors. Your xaml should look like this.
<ListView ItemsSource="{Binding Items}">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn att:Visibility.IsVisible="{Binding IsVisible}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
Also you don't need to implement INotifyPropertyChanged on your Item class. Your ObservableCollection already implements INotifyCollectionChanged to it already will update the UI. Your Item class should just like like this
public class Item
{
public string Text { get; set; }
public bool IsVisible { get; set; }
}

Referencing a UIElement in a ViewModel from XAML

I'm relatively new to using WPF and the MVVM architecture. I have a question about referencing UIelements from a XAML window's DataContext.
I have menu items that are bound to Views DataContext using this syntax:
<MenuItem Header="About" Command="{Binding AboutCommand}" />
I'd like to use a similar paradigm to add items to a grid. Right now I am using a class WorkflowDesigner. I can add it to my grid using the following code in my ViewModel:
grid.AddChildren(wd.View)
where view is of type UIElement.
What I'd rather do is add is reference to it from my XAML file without putting anything in my codebehind so that I can use the XAML mostly as a skin. Is it possible to use a tag just takes its UIElement from the datacontext of the XAML file?
This is possible, but it's not within the spirit of MVVM to have your ViewModel provide controls to your view. Ideally your ViewModel should have no dependencies on System.Windows.Controls at all.
If you must, then you can use a ContentControl:
<ContentControl Content={Binding wd.View} />
To handle this I'd create a ViewLocator class and put an instance of it into your resource dictionary. Then use this:
<ContentControl Content={Binding Source={StaticResource ViewLocator}, Path=WorkflowDesigner} />
I'm not sure if I quite understand your problem, but if you have a class you wish to present in your view from your ViewModel, you could use an ItemsControl to display different classes using a DataTemplate.
Say you have class User
public class User
{
public string Id { get; set;}
public string Name { get; set;}
}
public class UserViewModel
{
private ObservableCollectionaUser<User> _users = new......
public ObservableCollection<User> Users
{
get
{
return _users;
}
}
}
In UserView, you could have
<ItemsControl ItemsSource="{Binding Users}">
<ItemsControl.Resources>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Id}" />
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
This way, a User would be presented in the view using the template declared above. Then you would not have to use UIElements in your ViewModel.
The ItemsControl could refer to grid items, and have items presented in a grid with SharedGridScope (if I remember correctly).

How to bind silverlight datagrid combo box itemSource to viewModel

We're using Caliburn.Micro/Silverlight 4 and life is good.
I'm trying to bind a combobox's itemsSource to a viewModel, but this doesn't seem possible since the combobox is already bound to its own row's dataItem. The logic which fills the combo changes with other data on the screen so I can't really use a static list like I've been using.
Is there a way to bind directory to the viewModel somehow??? I've tried element to element binding but this never appears to work within the grid.
<Controls:DataGridTemplateColumn x:Name="FooNameCol" Header="Foo" MinWidth="200">
<Controls:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Foo.ShortName}"
Style="{StaticResource DataGridTextColumnStyle}"/>
</StackPanel>
</DataTemplate>
</Controls:DataGridTemplateColumn.CellTemplate>
<Controls:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox DisplayMemberPath="ShortName"
MinWidth="200" MinHeight="25"
SelectedItem="{Binding Path=Officer, Mode=TwoWay, ValidatesOnExceptions=True, NotifyOnValidationError=True}"
ItemsSource="{Binding Officers, Source={StaticResource ReferenceListRetriever}}" />
</DataTemplate>
</Controls:DataGridTemplateColumn.CellEditingTemplate>
</Controls:DataGridTemplateColumn>
Within a DataTemplate, the DataContext is bound to each single item of the corresponding list; since all Bindings implicitly refers to DataContext, you have to ensure that the path is valid, starting from the single data item.
In your scenario, for the indicated binding to work, you should have a VM shaped this way:
public class MyVM {
public IEnumerable<MyItem> Items {get;}
}
public class MyItem {
public Foo Foo {get;}
public Officer Officer {get;set;}
public IEnumerable<Officer> Officers {get;}
}
It may seem an overkill, but in some scenarios each combo can actually contain different choices for each data item, based on some business rule.
In simpler cases MyItem can just expose a common list coming from the parent MyVM:
public class MyItem {
...
public IEnumerable<Officer> Officers {
get { return _parent.AvailableOfficers; }
}
}
If you really can't live with it and prefer to keep the available Officers list in the root VM only, you can use a Xaml side trick:
public class MyVM {
public IEnumerable<MyItem> Items {get;}
public IEnumerable<Officer> Officers {get;}
}
public class MyItem {
public Foo Foo {get;}
public Officer Officer {get;set;}
}
Xaml:
<UserControl ...>
...
<AnyFrameworkElementAtThisLevel Name="bridge" />
...
<Controls:WhateverGrid>
...
<Controls:DataGridTemplateColumn ...>
<Controls:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
...
</DataTemplate>
</Controls:DataGridTemplateColumn.CellTemplate>
<Controls:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox DisplayMemberPath="ShortName"
SelectedItem="{Binding Path=Officer, Mode=TwoWay, ValidatesOnExceptions=True, NotifyOnValidationError=True}"
ItemsSource="{Binding DataContext.Officers, ElementName=bridge}" />
</DataTemplate>

WPF DataTemplate and Binding

I am DataTemplating a listbox's itemsource to display a label and combobox. In the, datatemplate I am assigning a new itemssource to the combobox but cant get it to work.
Or Ideally, how can I bind the combobox in a datatemplate to a different source.
Thanks. Mani
UserControl:
<DockPanel>
<ListBox x:Name="lstBox" ItemsSource="{Binding FilterControls}" />
</DockPanel>
<!--DataTemplate For SearchElement Type-->
<DataTemplate DataType="{x:Type CustomTypes:FilterElement}">
<Label> Text </Label>
*<ComboBox ItemsSource="{Binding Employees}"DisplayMemberPath="Sex" />*
</DataTemplate>
ViewModel:
List<FilterElement> FilterControls;
List<Employee> Employees
Class FilterElement
{
string Caption;
}
class Employee
{
string Sex;
}
In your combobox, you are binding to Employees in the current data context, which would be a FilterElement object - no Employees property to bind to.
In your binding, you probably want to set Source= to something else, which overrides your datacontext
There are lots of ways to set this one easy way to do this (easy to put here, anyway) would be to add a collectionViewSource to the resources of your window/control (I put Whatever.Resources - it can go in nearly any containing element)
<Whatever.Resources>
<CollectionViewSource x:Key="employeeSource" Source="{Binding Employees}">
</Whatever.Resources>
Then in your datatemplate
<ComboBox ItemsSource={Binding Source={StaticResource employeeSource}}" ... />
Note that using a CollectionViewSource will allow you to do sort/group in xaml as well.

WPF: ComboBoxes in ListBox and concurrency

I have code like this:
<ListBox ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock>Some Other Stuff Here</TextBlock>
<ComboBox ItemsSource="{Binding}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The problem is, every time the outside ListBox.SelectedItem gets changed, the ComboBoxes inside it would change their SelectedIndex to -1. Which means if I click "Some Other Stuff Here" (unless the ListBoxItem it is in is selected), all the comboboxes' selection get cleared.
How do I overcome this? Thx!
Presumably your combobox is bound to something like an ObservableCollection - try exposing an instance of ICollectionView instead:
class DataSource
{
// ...
public ObservableCollection<string> MyData { get; private set; }
public ICollectionView MyDataView
{
get
{
return CollectionViewSource.GetDefaultView(this.MyData);
}
}
}
You can then bind your combobox with:
<ComboBox ItemsSource="{Binding MyDataView}" IsSynchronizedWithCurrentItem="True" />
This means that the 'selected item' for each data source is stored in the ICollectionView object instead of within the combobox, which should mean that it is persisted when the ListBox SelectedItem changes

Resources