I use Listbox in my application.
<ListBox Name="lbMain">
<ListBox.ItemTemplate>
<DataTemplate>
<Expander Header="{Binding Converter={StaticResource convCaption},
Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" IsExpanded="True">
<StackPanel>
<TextBlock Text={Binding FirstName, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}>
</TextBlock>
<TextBlock Text={Binding SecondName, Mode=TwoWay, pdateSourceTrigger=PropertyChanged}>
</TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
code behind
public MainWindow()
{
MyModel myModel = new MyModel();
lbMain.ItemSource = myModel;
}
This is model
public class MyModel
{
public string FirstName{get; set; }
public string SecondName{get; set; }
}
This is converter (convCaption)
public object Convert(object value, Type targetType, object parameter ...)
{
MyModel model =(MyModel)value;
return string.Format("This is {0}, {1}", Model.FirstName, ModelSecondName)
}
It all works. But when I change FirstName or SecondName I need to change Header in Expander. If I write {Mode = TwoWay} show error - "need path". I like to write correctly binding for Expander(to headline the updated)?
You need to implement the INotifyPropertyChanged interface on your MyModel class. Please see the INotifyPropertyChanged Interface page on MSDN for help. Also, you don't really need a Converter to join the two names... there's a much simpler way... just override the ToString() method in that class too:
public override string ToString()
{
return string.Format("This is {0}, {1}", FirstName, SecondName);
}
First your MyModel needs to implement INotifyPropertyChanged and raise PropertyChanged event each time FirstName or SecondName has changed, like so:
public class MyModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
private string _firstName;
public string FirstName
{
get { return _firstName; }
set
{
if (_firstName == value) return;
_firstName = value;
OnPropertyChanged("FirstName");
}
}
private string _secondName;
public string SecondName
{
get { return _secondName; }
set
{
if (_secondName == value) return;
_secondName = value;
OnPropertyChanged("SecondName");
}
}
}
and then you can use MultiBinding with StringFormat like so:
<Expander>
<Expander.Header>
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}This is {0} {1}">
<Binding Path="FirstName"/>
<Binding Path="SecondName"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Expander.Header>
</Expander>
Related
I tried to bind a property to a nested object, but it fails.
I have taken a look at those questions, but i think i made another mistake somewhere else. Maybe someone can give i hind.
WPF: How to bind to a nested property?
binding to a property of an object
To upper slider/textbox has a correct binding while the lower one fails to do so.
I have two sliders with corrosponding textboxes:
<StackPanel>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBox Text="{Binding Path= boundnumber, Mode=TwoWay, FallbackValue='binding failed'}" ></TextBox>
<Slider Value="{Binding Path= boundnumber, Mode=TwoWay}" Width="500" Maximum="1000" ></Slider>
</StackPanel>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" DataContext="{Binding Path=myDatarow}">
<TextBox Text="{Binding Path= boundnumber, Mode=TwoWay, FallbackValue='binding failed'}" ></TextBox>
<Slider Value="{Binding Path= boundnumber, Mode=TwoWay}" Width="500" Maximum="1000" ></Slider>
</StackPanel>
</StackPanel>
Code behind:
public partial class MainWindow : INotifyPropertyChanged
{
public MainWindow()
{
DataContext = this;
InitializeComponent();
}
private int _boundnumber;
public int boundnumber
{
get { return _boundnumber; }
set
{
if (value != _boundnumber)
{
_boundnumber = value;
OnPropertyChanged();
}
}
}
Datarow myDatarow = new Datarow(11);
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyname = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyname));
}
}
class Datarow : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyname = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyname));
}
public Datarow()
{
}
public Datarow(int number)
{
boundnumber = number;
}
private int _boundnumber;
public int boundnumber
{
get { return _boundnumber; }
set
{
if (value != _boundnumber)
{
_boundnumber = value;
OnPropertyChanged();
}
}
}
}
You need to expose your myDatarow into a public property like your boundnumber.
private DataRow _myDatarow = new DataRow(11);
public DataRow myDataRow
{
get { return _myDatarow; }
}
And just an additional advice.
It's better to separate your DataContext class from the MainWindow.
I know I should use the MVVM pattern but I'm trying to get step by step closer to it. So here is my Listbox:
<ListBox x:Name="BoardList" ItemsSource="{Binding notes}" >
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<TextBox IsReadOnly="True" ScrollViewer.VerticalScrollBarVisibility="Visible" Text="{Binding text}" TextWrapping="Wrap" Foreground="DarkBlue"></TextBox>
<AppBarButton Visibility="{Binding visibility}" Icon="Globe" Click="OpenInBrowser" x:Name="Link"></AppBarButton>
<AppBarButton Icon="Copy" Click="Copy"></AppBarButton>
<AppBarButton Icon="Delete" Click="Delete"></AppBarButton>
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In the Mainpage.xaml.cs I declare the following:
ObservableCollection<BoardNote> notes = new ObservableCollection<BoardNote>();
So if I understood this right I don't need to care about the "INotifyCollectionChanged" stuff because I'm using an observablecollection?
So I got for example a textbox like this:
<Textbox x:Name="UserInputNote" Placeholdertext="Type in a text for your note"></Textbox>
And a button to Add the new note to the ObservableCollection and the click event is just like this:
notes.Add(new BoardNote(UserInputNote.Text));
So now the UI should update every time the user clicks the button to save a new note. But nothing happens. What did I do wrong?
If you need it here is the BoardNote class:
class BoardNote
{
public string text
{
get; set;
}
public BoardNote(string text)
{
this.text = text;
}
public Visibility visibility
{
get
{
if (text.StartsWith("http"))
return Visibility.Visible;
else
return Visibility.Collapsed;
}
}
}
You need to implement INotifyPropertyChanged. Here's one way of doing it.
Create this NotificationObject class.
public class NotificationObject : INotifyPropertyChanged
{
protected void RaisePropertyChanged<T>(Expression<Func<T>> action)
{
var propertyName = GetPropertyName(action);
RaisePropertyChanged(propertyName);
}
private static string GetPropertyName<T>(Expression<Func<T>> action)
{
var expression = (MemberExpression)action.Body;
var propertyName = expression.Member.Name;
return propertyName;
}
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Then your BoardNote class will inherit it this way:
class BoardNote : NotificationObject
{
private string _text
public string Text
{
get {return _text;}
set
{
if(_text == value) return;
_text = value;
RaisePropertyChanged(() => Text);
}
}
public BoardNote(string text)
{
this.text = text;
}
public Visibility visibility
{
get
{
if (text.StartsWith("http"))
return Visibility.Visible;
else
return Visibility.Collapsed;
}
}
}
I want to bind visibility of a textblock, within a listbox, to a bool value in my ViewModel. Binding works well to a textblock outside the listbox, but it isn't working to the textblock within the listbox. Please help!
xaml code:
<TextBlock x:Name="heading" Visibility="{Binding MyVb.Visible, Converter={StaticResource BoolToVisConverter}}" Width="480"/>
<ListBox x:Name="lstBani1" ItemsSource="{Binding Users}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock x:Name="tb1" Text="{Binding string1}" Visibility="{Binding MyVb.Visible, Converter={StaticResource BoolToVisConverter}}" Width="480"/>
<TextBlock x:Name="tb2" Text="{Binding string2}" Width="480"/>
<TextBlock x:Name="tb3" Text="{Binding string3}" Width="480"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
cs code:
public partial class Page1 : PhoneApplicationPage
{
public Page1()
{
ViewModel model = new ViewModel();
model.Users = GetUsers();
model.MyVb = new MyVisibility();
model.MyVb.Visible = false;
this.DataContext = model;
}
// View Model
public class ViewModel
{
public List<User> Users { get; set; }
public MyVisibility MyVb { get; set; }
}
// Bool to Visibility Converter
public class BooleanToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool)value ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
// Property Classes
public class User
{
public string string1 { get; set; }
public string string2 { get; set; }
public string string3 { get; set; }
}
public class MyVisibility : INotifyPropertyChanged
{
private bool _Visible;
public event PropertyChangedEventHandler PropertyChanged;
public bool Visible
{
get { return _Visible; }
set
{
_Visible = value;
NotifyPropertyChanged("Visible");
}
}
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
private List<Bani> GetUsers() {....}
}
Your ListBox is bound to Users, therefore each item in the list is has a DataContext bound to a User. Therefore your binding is trying to look for the property in the User class, not the parent data context.
Give your page a Name and change your binding to the following:
Visibility="{Binding DataContext.MyVb.Visible, ElementName=yourPageName, Converter={StaticResource BoolToVisConverter}}"
I recently started to learn the MVVM Pattern and created a simple application to test a few things.
I have a simple View with:
ListBox holding ObservableCollection of Items
Delete Button
New Button
TextBox for Item Description
TextBox for Item Value
Everything works except for the fact that, if i'm updating the item description the ListBox entry isn't updating. I read some articles about this, so i think it has something to do with CollectionChanged isn't called. I tried some possible solutions to this problem, but none of them worked. So maybe there is something generally wrong with my approach.
Hopefully someone can help me with this problem.
Model/Item.cs
internal class Item : INotifyPropertyChanged
{
#region Fields
private string value;
private string description;
#endregion
#region Constructors
public Item()
{
}
public Item(string value, string description) {
this.description = description;
this.value = value;
}
#endregion
public String Value
{
get
{
return value;
}
set
{
this.value = value;
OnPropertyChanged("Value");
}
}
public String Description
{
get
{
return description;
}
set
{
description = value;
OnPropertyChanged("Description");
}
}
#region Overrides
public override string ToString()
{
return description;
}
#endregion String Override
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
ViewModel/MainViewModel.cs
...
private ObservableCollection<Item> items;
private Item selectedItem;
public ObservableCollection<Item> Items {
get
{
return items;
}
set
{
items = value;
OnPropertyChanged("Items");
}
}
public Item SelectedItem {
get
{
return selectedItem;
}
set
{
selectedItem = value;
OnPropertyChanged("SelectedItem");
}
}
...
View/MainWindow.xaml
...
<Button Content="New" Command="{Binding NewCommand}" />
<Button Content="Delete" Command="{Binding DeleteCommand}" />
<ListBox x:Name="lbxItems" ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}" />
<TextBox Text="{Binding SelectedItem.Description}" />
<TextBox Text="{Binding SelectedItem.Value}" />
...
with this ItemTemplate it should work
<ListBox Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="2" ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Value}" Margin="0,0,10,0/>
<TextBlock Text="{Binding Path=Description}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
How you generate a PropertyChangedEvent in your model class?
try this:
internal class Range : INotifyPropertyChanged
{
private string _value;
public string Value
{
get { return _value; }
set
{
if (_value == value) return;
_value = value;
OnPropertyChanged("Value");
}
}
//Do same for the Description property
//Do not forgot implement INotifyPropertyChanged interface here
}
I hope this help you
Oh! After your update it is clearly. You does not use any datatemplate for listbox item, so WPF calls the ToString() method for show item without DT. But when you update any property, WPF does not know about this because object does not changing.
Use Datatemplate or try call OnPropertyChanged with empty string.
for the past couple of weeks I've been trying to get the EditableTextBlock (from codeproject) working on my TreeView.
The control has a property IsInEditMode which when set to true changes it to a TextBox.
The TreeView is virtualized and declared as follows:
<TreeView x:Name="treeEnvironment"
Margin="0,29,0,0" BorderThickness="0"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling">
</TreeView>
The TreeView uses the ItemsSource property to get it's data and the value of this is always a single instance of a class (lets call it A). This class contains a list of instances of another type (lets call it B). And this last class contains a list of instances of yet another class (lets call it `C). This is how it looks like in the code:
class A
{
public String Name;
public ObservableCollection<B> Items;
}
class B
{
public String Name;
public ObservableCollection<C> Items;
}
class C
{
public String Name;
public bool IsRenaming;
}
For each of these three classes there is an HierarchicalDataTemplate defined in MainWindow.Resources as follows:
<DataTemplate DataType="{x:Type C}">
<StackPanel Orientation="Horizontal">
<StackPanel.ContextMenu>
<ContextMenu>
<MenuItem Header="Rename" Click="C_Rename_Click" />
</ContextMenu>
</StackPanel.ContextMenu>
<v:EditableTextBlock Text="{Binding Path=Name}" IsInEditMode="{Binding Path=IsRenaming, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</DataTemplate>
<HierarchicalDataTemplate DataType="{x:Type B}" ItemsSource="{Binding Path=Items, Mode=OneWay}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" ToolTip="{Binding Path=Name}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type A}" ItemsSource="{Binding Path=Items, Mode=OneWay}">
<StackPanel Orientation="Horizontal">
<Image Source="icons/icon_A.png" Width="16" Height="16" />
<TextBlock Text="{Binding Path=Name}" ToolTip="{Binding Path=Name}" />
</StackPanel>
</HierarchicalDataTemplate>
None of the DataTemplate have keys so that it is applied automatically.
The event that is triggered when the rename MenuItem of C's context menu is clicked, is defined as follows:
private void C_Rename_Click(object sender, RoutedEventArgs e)
{
C instance = (sender as FrameworkElement).DataContext as C;
if (instance != null) {
instance.IsRenaming = true;
} else {
MessageBox.Show("DEBUG: C_Rename_Click(" + sender.ToString() + ", " + e.ToString() + ") : instance == null");
}
}
The problem is that the EditableTextBlock does not turn into a TextBox when the IsRenaming property is set to true on the instance of C that was chosen to be renamed.
The EditableTextBlock works just fine when I place it as a normal control.
My guess is that it has to do with virtualization. Any help would be appreciated.
Thank you for your time, best regards,
100GPing100.
class A, B, C need to implement INotifyPropertyChanged for any changes made to them to be propagated to the UI. You can either implement it in each class individually or have a base class implement INPC and derive your classes from this base class.
Something like:
public class MyBaseViewModel : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName) {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class A : MyBaseViewModel {
private string _name;
public string Name {
get {
return _name;
}
set {
_name = value;
OnPropertyChanged("Name");
}
}
private ObservableCollection<B> _items;
public ObservableCollection<B> Items {
get {
return _items;
}
set {
_items = value;
OnPropertyChanged("Items");
}
}
}
public class B : MyBaseViewModel {
private string _name;
public string Name {
get {
return _name;
}
set {
_name = value;
OnPropertyChanged("Name");
}
}
private ObservableCollection<C> _items;
public ObservableCollection<C> Items {
get {
return _items;
}
set {
_items = value;
OnPropertyChanged("Items");
}
}
}
public class C : MyBaseViewModel {
private string _name;
public string Name {
get {
return _name;
}
set {
_name = value;
OnPropertyChanged("Name");
}
}
private bool _isRenaming;
public bool IsRenaming {
get {
return _isRenaming;
}
set {
_isRenaming = value;
OnPropertyChanged("IsRenaming");
}
}
}
Now when you change IsRenaming in your code, you will see the update propagate to the UI and the TextBlock switch to a TextBox.
Side-note
Please have a look at MVVM. If you're not sure about it. Get to learn it slowly cos it helps UI development in WPF quite a bit.