I would like to bind a textbox to show the text of the item below the item that is selected.
Example:
Item 1 - Text = row numer one
Item 2 - Text = row number two
Item 3 - Text = row number three
I select item 2
output
Textbox 1 - Text = row number two (this is easily set up binding to selecteditem)
Textbox 2 - Text = row number three
I was thinking of a custom xpath of selectedindex + 1 but this doesn't seem to work
first attempt failed -- see below
You need to implement an IValueConverter and set it to the Converter attribute of the binding.
Create a class that inherits from IValueConverter, and in the Convert method, you'll cast the value parameter to ListBox (because you'll be binding the TextBox to the ListBox itself and letting the converter turn that into something meaningful).
Then get a reference to the ListBox's SelectedIndex property.
You want to return listBox.Items[selectedIndex + 1] from the method.
You can leave the ConvertBack method unimplemented.
You'll also have to handle the case where the last item in the ListBox is selected, because index + 1 will be out of bounds. Maybe you want to return the first item; maybe you want to return null or string.Empty.
update: custom ListBox
As requested, here is a sample that uses a custom ListBox with an additional [Dependency] property called "ItemAfterSelected."
First, the code for the derived control:
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication1
{
public class PlusOneListBox : ListBox
{
public PlusOneListBox()
{
SelectionMode = SelectionMode.Single;
}
public object ItemAfterSelected
{
get { return GetValue(ItemAfterSelectedProperty); }
set { SetValue(ItemAfterSelectedProperty, value); }
}
public static readonly DependencyProperty ItemAfterSelectedProperty = DependencyProperty.Register(
"ItemAfterSelected", typeof (object), typeof (PlusOneListBox));
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
var newly_selected = e.AddedItems;
if (newly_selected == null) ItemAfterSelected = null;
else
{
var last_index = Items.Count - 1;
var index = Items.IndexOf(newly_selected[0]);
ItemAfterSelected = index < last_index
? Items[index + 1]
: null;
}
base.OnSelectionChanged(e);
}
}
}
Here is a sample window that shows how to use and bind to the control (you can drop this in to an app and run it to see it in action).
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:custom="clr-namespace:WpfApplication1" xmlns:System="clr-namespace:System;assembly=mscorlib" Padding="24">
<StackPanel>
<custom:PlusOneListBox x:Name="custom_listbox">
<custom:PlusOneListBox.Items>
<System:String>one</System:String>
<System:String>two</System:String>
<System:String>three</System:String>
<System:String>four</System:String>
<System:String>five</System:String>
<System:String>six</System:String>
</custom:PlusOneListBox.Items>
</custom:PlusOneListBox>
<StackPanel Orientation="Horizontal" Margin="8">
<TextBlock Text="Selected: " />
<TextBlock Text="{Binding SelectedItem, ElementName=custom_listbox}" />
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="8">
<TextBlock Text="Next: " />
<TextBlock Text="{Binding ItemAfterSelected, ElementName=custom_listbox}" />
</StackPanel>
</StackPanel>
</Window>
Related
I have a DataGrid which is bound to an ObservableCollection ProductsFound
which is exposed as a property in my ViewModel.
By typing text in a TextBox, products contained in the model that have the Code property that contains the text inserted in the TextBox are added to ProductsFound.
I found out that if the DataGrid is contained in any control such as a StackPanel or a TabItem, the Window (the program) stops responding when I try to type text into the TextBox; while if the DataGrid isn't contained in any control, everything runs normally.
Here's the code for the window:
public class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// This method just fill the dataset I pass to the model's contructor in the next line.
Init();
ProductsModel model = new ProductsModel(dataSet);
searchViewModel = new ProductsSearchViewModel(model);
DataContext = searchViewModel;
}
private ProductsSearchViewModel searchViewModel;
// This handler supports the binding between the TextBox and the MatchText property of the View Model.
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
var binding = ((TextBox)sender).GetBindingExpression(TextBox.TextProperty);
binding.UpdateSource();
}
}
And here's my ViewModel:
public class ProductsSearchViewModel : Notifier, IProductsSearchViewModel
{
public ProductsSearchViewModel(IProductsModel inModel)
{
model = inModel;
productsFound = new ObservableCollection<ProductViewModel>();
}
private string matchText;
private IProductsModel model;
private ObservableCollection<ProductViewModel> productsFound;
// This is a helper method that search for the products in the model and adds them to ProductsFound.
private void Search(string text)
{
Results.Clear();
foreach (Product product in model.Products)
{
if (product.Code.ToLower().Contains(text.ToLower()))
Results.Add(new ProductViewModel(product));
}
}
public string MatchText
{
get { return matchText; }
// This setter is meant to be executed every time the Text property of the TextBox is changed.
set
{
if ((value != matchText) && (value != ""))
{
matchText = value;
// This raises INotifyPropertyChanged.PropertyChaged.
NotifyPropertyChanged("MatchText");
Search(value);
}
}
}
public ObservableCollection<ProductViewModel> ProductsFound
{
get
{
return productsFound;
}
set
{
productsFound = value;
NotifyPropertyChanged("Results");
}
}
}
Here's the XAML:
<Window x:Class="MyNameSpace.UI.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">
<Grid>
<StackPanel>
<TextBox Text="{Binding MatchText, Mode=TwoWay}" TextChanged="TextBox_TextChanged" />
<DataGrid x:Name="grid1" ItemsSource="{Binding Results}" >
</StackPanel>
</Grid>
With that StackPanel the program stops responding when I try to type text in the Textbox and no item is added to the DataGrid; but if i remove it everything runs ok.
What could the problem be? Am I missing something in how the WPF binding system works?
Is my view model coded wrong?
Thanks in advance.
Putting that StackPanel there prevents the DataGrid from acquiring a specific Height, thus it just expands down to infinity, and that breaks UI Virtualization.
Remove the StackPanel from there and use a non-infinite container, such as Grid or DockPanel.
I have some stackpanel that have under him 4 textblock that need to show some information.
I do some binding of the stackpanel ( DataContext ) and binding the textblock with the information that will hold by the object that was bind to the stackpanel.
I wrote the code + xaml and nothing work.
I get exception about format wrong.
The code:
public partial class SomeDemoClass: UserControl
{
classObjDemo c1;
public SomeDemoClass()
{
InitializeComponent();
c1 = new classObjDemo()
{
val1 = 5.5,
val2 = 2.3
};
}
}
The xaml ( that match the class 'SomeDemoClass' )
<StackPanel x:Name="LayoutRoot" DataContext="{Binding ElementName=SomeDemoClass, Path=c1">
<TextBlock Text="{Binding val1, StringFormat={0:F} }" />
<TextBlock Text="{Binding val2, StringFormat={0:F} }" />
</StackPanael>
Of you put x:Name="SomeDemoClass" in the in the op of your xaml and make c1 a public property instead of a field it would work. ElementName references elements in your xaml by name and binding only works on properties and dependency properties.
<UserControl x:Name="SomeDemoClass" ...
public classObjDemo c1 { get; set; }
Also check your Visual Studio output window for binding errors.
EDIT
Also make sure v1 and v2 of the classObjDemo are public properties
And escape { in your xaml. See http://elegantcode.com/2009/04/07/wpf-stringformat-in-xaml-with-the-stringformat-attribute/
<TextBlock Text="{Binding val2, StringFormat={}{0:F} }" />
I have a Textbox in a User Control i'm trying to update from my main application but when I set the textbox.Text property it doesnt display the new value (even though textbos.Text contains the correct data). I am trying to bind my text box to a property to get around this but I dont know how, here is my code -
MainWindow.xaml.cs
outputPanel.Text = outputText;
OutputPanel.xaml
<TextBox x:Name="textbox"
AcceptsReturn="True"
ScrollViewer.VerticalScrollBarVisibility="Visible"
Text="{Binding <!--?????--> }"/> <!-- I want to bind this to the Text Propert in OutputPanel.xmal.cs -->
OutputPanel.xaml.cs
namespace Controls
{
public partial class OutputPanel : UserControl
{
private string text;
public TextBox Textbox
{
get {return textbox;}
}
public string Text
{
get { return text; }
set { text = value; }
}
public OutputPanel()
{
InitializeComponent();
Text = "test";
textbox.Text = Text;
}
}
}
You have to set a DataContext in some parent of the TextBox, for example:
<UserControl Name="panel" DataContext="{Binding ElementName=panel}">...
Then the binding will be:
Text="{Binding Text}"
And you shouldn't need this - referring to specific elements from code behind is usually bad practice:
public TextBox Textbox
{
get {return textbox;}
}
I hope this example will help you.
1) Create UserControl.
2) Add to XAML <TextBlock Text="{Binding Path=DataContext.HeaderText}"></TextBlock>
3) In the code behind of that UserControl add
public partial class MyUserControl: UserControl
{
public string HeaderText { set; get; } // Add this line
public MyUserControl()
{
InitializeComponent();
DataContext = this; // And add this line
}
}
4) Outside of the control and let's say in the MainWindow Load event you have to do like
this.gdMain = new MyUserControl{ HeaderText = "YES" };
If your are starting to bind properties I suggest you check some articles on MVVM.
This is a very powerful architecture you can use on WPF. I found it very useful in my projects.
Check this one.
I've got a Combobox whose ItemTemplate is bound to a DataTemplate containing one of my custom label controls. All the custom control does is localize the content assigned to it.
The Combobox (when closed) will display the text of the first item selected. However when the selected item is changed, the display of the closed Combobox will not update. I know the actual selected item is updated because it's bound to a property that changes correctly. The only problem is the display text.
So for instance if I select the item with text 'Item 1' the closed Combobox will display 'Item 1'. Then if I select 'Item 2' the closed Combobox will still display 'Item 1'.
Here's how it's set up ('Name' is a property of the items being bound in the ItemsSource):
<Grid.Resources>
<DataTemplate x:Key="MyTemplate">
<MyCustomLabel Content="{Binding Name}" />
<DataTemplate>
</Grid.Resources>
<Combobox ItemsSource="{Binding MyItems}" ItemTemplate="{StaticResource MyTemplate}" />
Below is the code for my label control:
public class MyLabel : Label
{
/// <summary>
/// When reassigning content in the OnContentChanged method, this will prevent an infinite loop.
/// </summary>
private bool _overrideOnContentChanged;
protected override void OnContentChanged(object oldContent, object newContent)
{
// if this method has been called recursively (since this method assigns content)
// break out to avoid an infinite loop
if (_overrideOnContentChanged)
{
_overrideOnContentChanged = false;
return;
}
base.OnContentChanged(oldContent, newContent);
var newContentString = newContent as string;
if (newContentString != null)
{
// convert the string using localization
newContentString = LocalizationConverter.Convert(newContentString);
// override the content changed method
// will prevent infinite looping when this method causes itself to be called again
_overrideOnContentChanged = true;
Content = newContentString;
}
}
}
Any advice would be greatly appreciated. Thanks!
Perform a 2-way databinding on the SelectedItem property of the combo box.
The target property that you're binding the combo-box to - should raise a PropertyChanged event.
<ComboBox ItemsSource={Binding Path=Source} SelectedItem={Binding Path=CurrentItem, Mode=TwoWay}/>
class ComboContext : INotifyPropertyChanged
{
public List<object> Source { get; set; } // attach your source here
public object CurrentItem { get { return mCurrentItem; } set { mCurrentItem = value; OnPropertyChanged("CurrentItem"); } } // bind to this property
private object mCurrentItem;
void OnPropertyChanged(string name)
{
if(PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
The code sample might not be "complete" - it's 1am and I'm kinda tired, but it should put you on the right track.
Edit2.
Just noticed your template, it's wrong, the whole idea is wrong.
<ComboBox ItemsSource={Binding Path=Source} SelectedItem={Binding Path=CurrentItem, Mode=TwoWay}>
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text={Binding}/>
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Notice that the texblock's text is a binding with no path, (and it's a default binding too.) the no-path binding means that this textblock will bind to whatever is directly "underneath".
It seems nobody has yet found a way to set the comboboxitem as selected with a SelectedItem="Binding Property".
Is the solution to use a IsSelected Property in the ViewModel object within the combobox itemssource?
Our successful approach for binding a combobox is the following...
<ComboBox
ItemsSource="{Binding Path=AllItems}"
SelectedItem="{Binding Path=CurrentItem, Mode=TwoWay}" />
<TextBlock Text="{Binding Path=CurrentItem, Mode=TwoWay}" />
class public ItemListViewModel
{
public ObservableCollection<Item> AllItems {get; set;}
private Item _currentItem;
public Item CurrentItem
{
get { return _currentItem; }
set
{
if (_currentItem == value) return;
_currentItem = value;
RaisePropertyChanged("CurrentItem");
}
}
}
Not sure why you can't data bind to SelectedItem on a ComboBox without seeing your code. Below shows you how to do it using a CollectionView which has current item management built in which comboboxes supports. CollectionView has a CurrentItem get property you can use to get currently selected.
XAML:
<Window x:Class="CBTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<StackPanel>
<ComboBox
ItemsSource="{Binding Path=Names}"
IsSynchronizedWithCurrentItem="True">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Text="{Binding Path=Names.CurrentItem}" />
</StackPanel>
</Window>
Code behind:
using System.Collections.Generic;
using System.Windows;
using System.Windows.Data;
namespace CBTest
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = new VM();
}
}
public class VM
{
public VM()
{
_namesModel.Add("Bob");
_namesModel.Add("Joe");
_namesModel.Add("Sally");
_namesModel.Add("Lucy");
Names = new CollectionView(_namesModel);
// Set currently selected item to Sally.
Names.MoveCurrentTo("Sally");
}
public CollectionView Names { get; private set; }
private List<string> _namesModel = new List<string>();
}
}
what i found is that in combobox soure code, selecteditem is set by using list selectedindex
combobox using
public object SelectedItem {
get {
int index = SelectedIndex;
return (index == -1) ? null : Items[index];
}
set {
int x = -1;
if (itemsCollection != null) {
//bug (82115)
if (value != null)
x = itemsCollection.IndexOf(value);
else
SelectedIndex = -1;
}
if (x != -1) {
SelectedIndex = x;
}
}
}
this method always return -1 or null every time you set Selecteditem by code
x = itemsCollection.IndexOf(value);
its reported as bug (82115) in combobox code
so the working method is to use SelectedIndex directly and bind to it instead of SelectemItem property and if you want you can read only the item from binding to the SelectedItem property or obtain it in your code using ItemsSource itself.
This working for me fine.