My WPF Application code generates panels on function call defined in .cs file. There is ItemControl used in code to generates these Panels . I want to Name Textbox defined in this ItemControl and to use this in code. I named it as textEdit1 and used it in code but code generated error that textEdit1 doesn't exist. Can anyone solve my problem? Here Code is:
XAML File:
<dxlc:ScrollBox>
<ItemsControl Name="lstPanels">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="vertical">
<Grid>
<dxe:TextEdit Height="165" Text="{Binding Text,
Mode=TwoWay}" x:Name="textEdit1"/>
</Grid>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</dxlc:ScrollBox>
.CS FILE
public partial class Window1 : Window
{
string valuu;
public Window1()
{
InitializeComponent();
addPanel("Header1");
addPanel("Header2");
addPanel("Header3");
lstPanels.ItemsSource = panels;
}
public ObservableCollection<MyPanel> panels = new ObservableCollection<MyPanel>();
public void addPanel(string buttonId)
{
MyPanel p = new MyPanel { Id = buttonId};
panels.Add(p);
functionb(p);
}
public void functionb(MyPanel obj)
{
valuu = obj.Text;
}
private void button2_Click(object sender, RoutedEventArgs e)
{
foreach (var f in panels.ToList())
{
MessageBox.Show( f.Id + " *** " + f.Text);
}
}
}
public class MyPanel : INotifyPropertyChanged
{
private string _id;
private string _text;
public string Id
{
get { return _id; }
set
{
if (value != _id)
{
_id = value;
NotifyPropertyChanged();
}
}
}
public string Text
{
get { return _text; }
set
{
if (value != _text)
{
_text = value;
NotifyPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged( String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I see that you are using some 3rd party libraries for your TextBox and ScrollBox. If you provide me with the names of the libraries, I could have a look at them as the functionality might be different from what WPF has out-of-the-box.
As for now you have 3 options (I am giving examples for standard TextBox and ItemsControl):
I) You do not have to access the textbox at all.
An easy way around it is described here: StackOverflow post
II) Handling events and references to TextBoxes in the code behind
Add a Loaded event to your TextBox:
<TextBox x:Name="txtText" Width="300" Height="100" Loaded="txtText_Loaded" />
Add a field to your MyPanel class to hold a reference to a TextBox:
public class MyPanel
{
public string Text { get; set; }
public TextBox TextBox { get; set; }
/* the rest ... */
}
Add a counter to your window, next to a list with panels:
protected ObservableCollection<MyPanel> panels = new ObservableCollection<MyPanel>();
private int counter = 0;
Handle the Load event of the TextBox:
private void txtText_Loaded(object sender, RoutedEventArgs e)
{
panels[counter].TextBox = (TextBox)sender;
counter++;
}
If you want to access a particular TextBox, do it this way:
MessageBox.Show(panels[i].TextBox.Text);
III) Add additional bindings for FontSize:
Add a FontSize property to your MyPanel class:
private double _fontSize = 10;
public double FontSize
{
get { return _fontSize; }
set
{
if (value != _fontSize)
{
_fontSize = value;
NotifyPropertyChanged();
}
}
}
Bind just added property to the TextBox in your ItemsControl:
<TextBox x:Name="txtText" Width="300" Height="100" Text="{Binding Text;, Mode=TwoWay}"
FontSize="{Binding FontSize, Mode=OneWay}" />
Add a slider to the template and bind it to the same property:
<Slider Minimum="10" Maximum="30" Value="{Binding FontSize, Mode=TwoWay}" />
This way if you change the value on a slider, it will change the value in your MyPanel object bound to the panel. This in turn will change the font size of the textbox.
My whole code I tested it on looks like that:
<ItemsControl x:Name="lstItems" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBox x:Name="txtText" Width="300" Height="100" Text="{Binding Text;, Mode=TwoWay}" FontSize="{Binding FontSize, Mode=OneWay}" />
<Slider Minimum="10" Maximum="30" Value="{Binding FontSize, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
And code behind:
public partial class MainWindow : Window
{
protected ObservableCollection<MyPanel> texts = new ObservableCollection<MyPanel>();
public MainWindow()
{
InitializeComponent();
texts.Add(new MyPanel() { Text = "Test 1" });
texts.Add(new MyPanel() { Text = "Test 2" });
lstItems.ItemsSource = texts;
}
}
public class MyPanel : INotifyPropertyChanged
{
private string _id;
private string _text;
private double _fontSize = 10;
public string Id
{
get { return _id; }
set
{
if (value != _id)
{
_id = value;
NotifyPropertyChanged();
}
}
}
public string Text
{
get { return _text; }
set
{
if (value != _text)
{
_text = value;
NotifyPropertyChanged();
}
}
}
public double FontSize
{
get { return _fontSize; }
set
{
if (value != _fontSize)
{
_fontSize = value;
NotifyPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I personally would go with the last solution.
But again, let me know what libraries you are using, and I will have look at them when I have some time. Good luck.
textEdit1 is part of a template that will be instantiated multiple times, so there will be multiple instances of textEdit1. It wouldn't make sense to generate a field for textEdit1 in the class, because it could only refer to one instance the TextEdit control...
Related
Question
How can I make it so that changes to a note are only propagated back to the list, when the Save button is clicked instead on "lost focus"?
And the Save button should only be enabled when the note has been changed.
UI
The example application looks like this:
The current behaviour is:
Clicking on a note puts its text into the TextBox; that's fine.
The changed text from the TextBox gets written back to the list when the TextBox loses the focus (default binding behaviour); but I only want that to happend when the Save button is clicked.
The Save button is always activated because the CanExecute(object parameter) isn't correctly implemented yet; it should only get activated when the TextBox text is different from the selected note's text.
My research so far
Option 1: Some Internet sources say to bind a different property to the TextBox and to programmatically check whether it is different from the SelectedItem of the ListView. I would have hoped that there was a way without introducing a third property in addition to the already existing ListOfNotes and SelectedNote.
Option 2: Some Internet sources recommend to configure Mode=OneWay so that clicking an item in the ListView updates the TextBox, but not the other way around. This sounds like the solution I would prefer, but I wasn't able to figure out from the code examples how to raise an event programmatically so that the change in the TextBox gets written back to the ListView when the Save button is clicked.
I've found other Stackoverflow questions that seem to be similar to mine, but the answers to those haven't helped me fix the problem:
WPF databinding after Save button click
Code
This example currently does two-way binding on focus lost. How do I need to change it to get the above described behaviour?
https://github.com/lernkurve/WpfBindingOneWayWithSaveButton
MainWindow.xaml
<Window x:Class="WpfBindingOneWayWithSaveButton.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:wpfBindingOneWayWithSaveButton="clr-namespace:WpfBindingOneWayWithSaveButton"
mc:Ignorable="d"
Title="MainWindow" Height="188.636" Width="299.242">
<Window.DataContext>
<wpfBindingOneWayWithSaveButton:MainWindowsViewModel />
</Window.DataContext>
<Grid>
<GroupBox Header="List of notes" HorizontalAlignment="Left" VerticalAlignment="Top" Height="112" Width="129" Margin="0,24,0,0">
<ListView ItemsSource="{Binding ListOfNotes}" SelectedItem="{Binding SelectedNote}" DisplayMemberPath="Text" HorizontalAlignment="Left" Height="79" VerticalAlignment="Top" Width="119" Margin="0,10,-2,0"/>
</GroupBox>
<GroupBox Header="Change selected note" HorizontalAlignment="Left" Margin="134,24,0,0" VerticalAlignment="Top" Height="112" Width="151">
<Grid HorizontalAlignment="Left" Height="89" Margin="0,0,-2,0" VerticalAlignment="Top" Width="141">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40*"/>
<ColumnDefinition Width="101*"/>
</Grid.ColumnDefinitions>
<TextBox Text="{Binding SelectedNote.Text}" HorizontalAlignment="Left" Height="23" TextWrapping="Wrap" VerticalAlignment="Top" Width="121" Margin="10,7,0,0" Grid.ColumnSpan="2"/>
<Button Command="{Binding SaveCommand}" Content="Save" HorizontalAlignment="Left" VerticalAlignment="Top" Width="121" Margin="10,35,0,0" Grid.ColumnSpan="2"/>
</Grid>
</GroupBox>
</Grid>
</Window>
MainWindowsViewModel.cs
using System.Collections.ObjectModel;
using System.Windows.Input;
namespace WpfBindingOneWayWithSaveButton
{
public class MainWindowsViewModel
{
public ObservableCollection<Note> ListOfNotes { get; set; }
public Note SelectedNote { get; set; }
public ICommand SaveCommand { get; set; }
public MainWindowsViewModel()
{
ListOfNotes = new ObservableCollection<Note>
{
new Note { Text = "Note 1" },
new Note { Text = "Note 2" }
};
SaveCommand = new SaveCommand(this);
}
}
}
SaveCommand.cs
using System;
using System.Windows.Input;
namespace WpfBindingOneWayWithSaveButton
{
public class SaveCommand : ICommand
{
private MainWindowsViewModel vm;
public SaveCommand(MainWindowsViewModel vm)
{
this.vm = vm;
}
public bool CanExecute(object parameter)
{
// What should go here?
return true;
// Pseudo code
// return (is the TextBox text different from the original note text)
}
public void Execute(object parameter)
{
// What should go here?
// Pseudo code
// Let WPF know that the TextBox text has changed
// Invoke the binding so it propagates the TextBox text back to the list
}
public event EventHandler CanExecuteChanged;
}
}
Note.cs
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace WpfBindingOneWayWithSaveButton
{
public class Note : INotifyPropertyChanged
{
private string text;
public string Text
{
get { return text; }
set
{
text = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Bind the text to the CommandParameter of the SaveButton so it gets passed to the Save method for updating.
<TextBox x:Name="NoteTextBox" Text="{Binding SelectedNote.Text, Mode=OneTime}" ../>
<Button Command="{Binding SaveCommand}"
CommandParameter="{Binding ElementName=NoteTextBox, Path=Text}",
Content="Save" />
and
public bool CanExecute(object parameter)
{
return vm.SelectedNote.Text != parameter as string;
}
public void Execute(object parameter)
{
vm.SelectedNote.Text = parameter as string;
}
Option one is the easiest to implement, you will need to clone the Note object and set it to a separate property.
in your xaml, change your list view to the following so it now binds the SelectedIndex instead of the SelectedItem.
<ListView ItemsSource="{Binding ListOfNotes}" SelectedIndex="{Binding SelectedIndex}" DisplayMemberPath="Text" ...
And change TextBox to the following so it updates the binding as you type
<TextBox Text="{Binding Path=SelectedNote.Text, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" ...
In Note.cs we add the Clone() method.
public class Note : INotifyPropertyChanged
{
public Note Clone()
{
return new Note()
{
Text = this.Text
};
}
//... The rest stays the same
}
In MainWindowsViewModel.cs we add new properties for the SelectedIndex and clone the object when we detect a index has changed. We also need to add INotifyPropertyChanged so we can update the SelectedNote from the codebehind when we do the Clone()
public class MainWindowsViewModel : INotifyPropertyChanged
{
private int _selectedIndex = -1;
private Note _selectedNote;
public int SelectedIndex
{
get { return _selectedIndex; }
set
{
if (_selectedIndex.Equals(value))
return;
_selectedIndex = value;
CloneSelectedNote();
}
}
private void CloneSelectedNote()
{
if (SelectedIndex >= 0)
{
SelectedNote = ListOfNotes[SelectedIndex].Clone();
}
else
{
SelectedNote = null;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public Note SelectedNote
{
get { return _selectedNote; }
set
{
if(Equals(_selectedNote, value))
return;
_selectedNote = value;
OnPropertyChanged();
}
}
//... The rest stays the same
}
In SaveCommand.cs we add the logic for CanExecute and add the subscriptions to CommandManager.RequerySuggested, this automatically makes it requery the CanExecute any time any binding changes. This can be a little ineffecent, if you wanted to you could expose a RaiseCanExecuteChanged() publicly but it would be MainWindowsViewModel responsibility to call it any time vm.SelectedIndex or vm.SelectedNote.Text changed.
public class SaveCommand : ICommand
{
private MainWindowsViewModel vm;
public SaveCommand(MainWindowsViewModel vm)
{
this.vm = vm;
}
public bool CanExecute(object parameter)
{
if (vm.SelectedIndex < 0 || vm.SelectedNote == null)
return false;
return vm.ListOfNotes[vm.SelectedIndex].Text != vm.SelectedNote.Text;
}
public void Execute(object parameter)
{
vm.ListOfNotes[vm.SelectedIndex] = vm.SelectedNote;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
UPDATE: Here is a updated version that does not use CommandManager
MainWindowsViewModel.cs
public class MainWindowsViewModel : INotifyPropertyChanged
{
private int _selectedIndex = -1;
private Note _selectedNote;
public int SelectedIndex
{
get { return _selectedIndex; }
set
{
if (_selectedIndex.Equals(value))
return;
_selectedIndex = value;
CloneSelectedNote();
RecheckSaveCommand();
}
}
private void CloneSelectedNote()
{
if (SelectedIndex >= 0)
{
SelectedNote = ListOfNotes[SelectedIndex].Clone();
}
else
{
SelectedNote = null;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public Note SelectedNote
{
get { return _selectedNote; }
set
{
if(Equals(_selectedNote, value))
return;
if (_selectedNote != null)
{
PropertyChangedEventManager.RemoveHandler(_selectedNote, SelectedNoteTextChanged, nameof(Note.Text));
}
_selectedNote = value;
if (_selectedNote != null)
{
PropertyChangedEventManager.AddHandler(_selectedNote, SelectedNoteTextChanged, nameof(Note.Text));
}
OnPropertyChanged();
}
}
private void SelectedNoteTextChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
{
RecheckSaveCommand();
}
private void RecheckSaveCommand()
{
var command = this.SaveCommand as WpfBindingOneWayWithSaveButton.SaveCommand; //"this." and "WpfBindingOneWayWithSaveButton." are not necessary but I wanted to be explicit.
if (command != null)
{
command.RaiseCanExecuteChanged();
}
}
//...
}
SaveCommand.cs
public class SaveCommand : ICommand
{
private MainWindowsViewModel vm;
public SaveCommand(MainWindowsViewModel vm)
{
this.vm = vm;
}
public bool CanExecute(object parameter)
{
if (vm.SelectedIndex < 0 || vm.SelectedNote == null)
return false;
return vm.ListOfNotes[vm.SelectedIndex].Text != vm.SelectedNote.Text;
}
public void Execute(object parameter)
{
vm.ListOfNotes[vm.SelectedIndex] = vm.SelectedNote;
}
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
You should not use OneWay but rather an UpdateSourceTrigger of value Explicit. BindingGroups can do this for you though, here's a simple example:
<!-- For change observation -->
<TextBlock Text="{Binding Text}"></TextBlock>
<StackPanel>
<StackPanel.BindingGroup>
<BindingGroup x:Name="EditGroup"></BindingGroup>
</StackPanel.BindingGroup>
<TextBox Text="{Binding Text}"></TextBox>
<Button>
<Button.Command>
<local:CommitGroupCommand BindingGroup="{x:Reference EditGroup}"/>
</Button.Command>
Save
</Button>
</StackPanel>
public class CommitGroupCommand : ICommand
{
public BindingGroup BindingGroup { get; set; }
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
BindingGroup.UpdateSources();
}
}
(You could add a validation rule to your binding that requires the value to be different and use that for the CanExecute implementation.)
Using this method allows you to bind directly to the object you intend to edit, so you don't need to copy around values first.
I've tried solving this myself, looking at several possible solutions here on Stack Overflow, but alas I've been unable to solve this issue.
TL;DR version:
The problem:
A listBox using databinding to show a list of RPG characters, which have a nested property for their attributes. I can't get the attributes to show due to the limitations with nested properties and databindings.
The code below is related to the issue.
I have a listBox that has a databinding that controls what is shown in the list. The databinding uses ObservableCollection for the list of objects that the list contains. All this works fine, but is related to the issue at hand.
The listBox databinding has several nested properties in each element, that I want to display and change in the form, yet I cannot get nested databinding to work correctly.
This is the listBox XAML:
<ListBox x:Name="listCharacters" Margin="2,0" ItemsSource="{Binding}" Grid.Row="1" ScrollViewer.VerticalScrollBarVisibility="Visible" ScrollViewer.HorizontalScrollBarVisibility="Hidden" SelectionChanged="listCharacters_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type local:Character}" x:Name="Symchar">
<Grid Width="125" HorizontalAlignment="Left" Background="{x:Null}">
<Grid.RowDefinitions>
<RowDefinition Height="18"/>
<RowDefinition Height="12"/>
<RowDefinition Height="16"/>
</Grid.RowDefinitions>
<Image Panel.ZIndex="5" HorizontalAlignment="Right" VerticalAlignment="Top" Height="16" Width="16" Margin="0,2,0,0" Source="Resources/1454889983_cross.png" MouseUp="DeleteCharacter" />
<TextBlock Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" FontWeight="Bold" FontSize="13.333" Grid.Row="0" TextTrimming="CharacterEllipsis" Padding="0,0,16,0" />
<TextBlock Text="{Binding RealRace.Label, UpdateSourceTrigger=PropertyChanged}" FontSize="9.333" Grid.Row="1" FontStyle="Italic" />
<TextBlock FontSize="9.333" Grid.Row="2" HorizontalAlignment="Left" VerticalAlignment="Top">
<Run Text="{Binding RealClass.Archetype.Label, UpdateSourceTrigger=PropertyChanged}"/>
<Run Text=" - "/>
<Run Text="{Binding RealClass.Label, UpdateSourceTrigger=PropertyChanged}"/>
</TextBlock>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And setting the listBox ItemSource:
this.listCharacters.ItemsSource = CharacterList;
This is the character class, I removed unrelated code (XML serialization attributes etc.)
public class Character : INotifyPropertyChanged
{
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
this.NotifyPropertyChanged("Name");
}
}
private string _player;
public string Player
{
get { return _player; }
set
{
_player = value;
this.NotifyPropertyChanged("Player");
}
}
private string _race;
public string Race
{
get { return _race; }
set
{
_race = value;
this.NotifyPropertyChanged("Race");
}
}
private Race _realRace;
public Race RealRace
{
get { return _realRace; }
set
{
_realRace = value;
Race = value.Id;
this.NotifyPropertyChanged("RealRace");
}
}
private string _gender;
public string Gender
{
get { return _gender; }
set
{
_gender = value;
this.NotifyPropertyChanged("Gender");
}
}
private Attributes _attributes;
public Attributes Attributes
{
get { return _attributes; }
set
{
_attributes = value;
this.NotifyPropertyChanged("Attributes");
}
}
private string _class;
public string Class
{
get { return _class; }
set
{
_class = value;
this.NotifyPropertyChanged("Class");
}
}
private Class _realClass;
public Class RealClass
{
get { return _realClass; }
set
{
_realClass = value;
Class = value.Id;
this.NotifyPropertyChanged("RealClass");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
To keep it simple, the property that I've been testing with, is the 'Attributes' property, this is the code for it:
public class Attributes : INotifyPropertyChanged
{
private int _accurate;
public int Accurate
{
get { return _accurate; }
set
{
_accurate = value;
this.NotifyPropertyChanged("Accurate");
}
}
private int _cunning;
public int Cunning
{
get { return _cunning; }
set
{
_cunning = value;
this.NotifyPropertyChanged("Cunning");
}
}
private int _discreet;
public int Discreet
{
get { return _discreet; }
set
{
_discreet = value;
this.NotifyPropertyChanged("Discreet");
}
}
private int _persuasive;
public int Persuasive
{
get { return _persuasive; }
set
{
_persuasive = value;
this.NotifyPropertyChanged("Persuasive");
}
}
private int _quick;
public int Quick
{
get { return _quick; }
set
{
_quick = value;
this.NotifyPropertyChanged("Quick");
}
}
private int _resolute;
public int Resolute
{
get { return _resolute; }
set
{
_resolute = value;
this.NotifyPropertyChanged("Resolute");
}
}
private int _strong;
public int Strong
{
get { return _strong; }
set
{
_strong = value;
this.NotifyPropertyChanged("Strong");
}
}
private int _vigilant;
public int Vigilant
{
get { return _vigilant; }
set
{
_vigilant = value;
this.NotifyPropertyChanged("Vigilant");
}
}
private int _toughness;
public int Toughness
{
get { return _toughness; }
set
{
_toughness = value;
this.NotifyPropertyChanged("Toughness");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
I want to display each individual attribute in a field when a character in the listBox is selected, this works fine with properties directly in the character class, but due to the limitations on nested properties and databindings, I haven't been able to get it to work with the 'Attributes' properties values.
XAML for one of the attribute input fields:
<TextBox x:Name="attr_Accurate" DataContext="{Binding Path=(local:Character.Attributes), XPath=SelectedItem, ElementName=listCharacters, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Text="{Binding Path=(local:Accurate)}" PreviewTextInput="NumericInput"/>
The UpdateSourceTrigger is simply a method to only allow integers to be input in the field:
private void NumericInput(object sender, TextCompositionEventArgs e)
{
if (!char.IsDigit(e.Text, e.Text.Length - 1))
{
e.Handled = true;
}
}
If anyone could help me get the values within the selected character's attributes to show up via databindings, I would greatly appreciate it.
Use binding as following:
To Show Accurate Property value:
<TextBox x:Name="attr_Accurate" Text="{Binding Path=SelectedItem.Attributes.Accurate), ElementName=listCharacters, Mode=OneWay}" PreviewTextInput="NumericInput"/>
To Show Cunning property value:
<TextBox x:Name="attr_Accurate" Text="{Binding Path=SelectedItem.Attributes.Cunning), ElementName=listCharacters, Mode=OneWay}" PreviewTextInput="NumericInput"/>
and so one.
(I'm not sure if you want binding to be two way or one so please
change as your need)
I have a ListBox on WP8.1 and want to Bind some items in there. That works all fine, but changing a value on the ItemSource doesn't change anything in the ListBox
<ListBox x:Name="myListBox" Width="Auto" HorizontalAlignment="Stretch" Background="{x:Null}" Foreground="{x:Null}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel x:Name="PanelTap" Tapped="PanelTap_Tapped">
<Border x:Name="BorderCollapsed">
<StackPanel Margin="105,0,0,0">
<TextBlock Text="{Binding myItem.location, Mode=TwoWay}" />
</StackPanel>
</Border>
</ListBox.ItemTemplate>
</ListBox>
I bind the items via
ObservableCollection<LBItemStruct> AllMyItems = new ObservableCollection<LBItemStruct>();
with
public sealed class LBItemStruct
{
public bool ext { get; set; }
public Container myItem { get; set; }
}
public sealed class Container
{
public string location{ get; set; }
...
}
and when I now want to change the TextBlock Text, nothing happens
private void PanelTap_Tapped(object sender, TappedRoutedEventArgs e)
{
int sel = myListBox.SelectedIndex;
if (sel >= 0)
{
myListBox[sel].myItem.location = "sonst wo";
}
}
The PanelTap_Tapped gets triggered, when I tap the Panel (checked via Debug), but the TextBlock Text does not change
If you want the view to update when a property changes, then you need to have the source object implement INotifyPropertyChaned, and raise the PropertyChanged event:
public sealed class Container : INotifyPropertyChanged
{
public string location
{
get { return _location; }
set { _location = value; RaisePropertyChanged("location"); }
}
private string _location;
...
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propName)
{
var handler = PropertyChanged;
if (handler != null)
handler(new PropertyChangedEventArgs(this, propName));
}
}
My code generates panels on function call defined in .cs file. There is ItemControl used in code to generates these Panels . Each Panel has it's Textbox , Slider and Combobox.
Each Panel's Slider and Combobox is playing with TextBox.Text Like:
Slider to increase Textbox.Text Font Size.
Combobox to Select alignment of TextBox.Text.
I want to replace Combobox with Button with Content Left. So as i click on Button it's Content must change to Right and similarly from Right to Left., to make changes to Alignment.
Can anyone solve my problem? Here Code is:
XAML FILE:
<ItemsControl x:Name="lstItemsClassM" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<ComboBox x:Name="cboOccupation" IsEditable="False" HorizontalAlignment="Left"
Text="{Binding Path=Alignment, Mode=TwoWay}"
Margin="4" Width="140">
<ComboBoxItem>Right</ComboBoxItem>
<ComboBoxItem>Left</ComboBoxItem>
</ComboBox>
<Button Content="{Binding Alignment, Mode=TwoWay}" Click="Button_Click" Tag="{Binding PKId}" SourceUpdated="Button_SourceUpdated" />
<TextBox x:Name="txtText" Width="300" Height="100" Text="{Binding Text;, Mode=TwoWay}" FontSize="{Binding FontSize, Mode=OneWay}" TextAlignment="{Binding Alignment, Mode=OneWay}" />
<Slider Minimum="10" Maximum="30" Value="{Binding FontSize, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
.cs File
public partial class Window2 : Window
{
protected ObservableCollection<ClassM> texts = new ObservableCollection<ClassM>();
int dv;
public Window2()
{
InitializeComponent();
dv=1;
texts.Add(new ClassM() { PKId=dv, Text = "Test 1" });
dv=2;
texts.Add(new ClassM() { PKId=dv, Text = "Test 2" });
lstItemsClassM.ItemsSource = texts;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var myValue = ((Button)sender).Tag;
foreach (var f in texts.ToList())
{
if (f.PKId.ToString() == myValue.ToString())
{
f._alignment = "Right";
MessageBox.Show(f._alignment);
}
}
}
private void Button_SourceUpdated(object sender, DataTransferEventArgs e)
{
var myValue = ((Button)sender).Tag;
foreach (var f in texts.ToList())
{
if (f.PKId.ToString() == myValue.ToString())
{
f._alignment = "Right";
MessageBox.Show(f._alignment);
}
}
}
}
public class ClassM : INotifyPropertyChanged
{
private string _id;
private int _pkid;
private string _text;
private double _fontSize = 10;
public bool _isChecked { get; set; }
public string _alignment="Left";
public int PKId
{
get { return _pkid; }
set
{
if (value != _pkid)
{
_pkid = value;
NotifyPropertyChanged();
}
}
}
public string Id
{
get { return _id; }
set
{
if (value != _id)
{
_id = value;
NotifyPropertyChanged();
}
}
}
public bool IsChecked
{
get { return _isChecked; }
set
{
if (value != _isChecked)
{
_isChecked = value;
NotifyPropertyChanged();
}
}
}
public string Text
{
get { return _text; }
set
{
if (value != _text)
{
_text = value;
NotifyPropertyChanged();
}
}
}
public double FontSize
{
get { return _fontSize; }
set
{
if (value != _fontSize)
{
_fontSize = value;
NotifyPropertyChanged();
}
}
}
public string Alignment
{
get { return _alignment; }
set
{
if (value != _alignment)
{
_alignment = value;
NotifyPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
The TextBox.TextAlignment property is of the enumeration type System.Windows.TextAlignment.
If you want to bind it without having to implement your own value converter then you want to populate the ComboBox with the actual enum values, not just a string with the same name like "Left" and "Right"...
Here's how you'd make a ComboBox represent TextAlignment:
<ComboBox>
<ComboBox.Items>
<TextAlignment>Left</TextAlignment>
<TextAlignment>Right</TextAlignment>
</ComboBox.Items>
</ComboBox>
Now you can bind the SelectedItem property of the ComboBox to the TextAlignment property of the TextBox.
Or in your case if you want to bind both to an underlying data context then you would need to change this:
private string _alignment = "Left";
To this:
private TextAlignment _alignment = TextAlignment.Left;
I'm really new to WPF so apologies in adavnced if this is an obvious question. I have a simple Checkbox in XAML as
<ListBox ScrollViewer.VerticalScrollBarVisibility="Auto"
ItemsSource="{Binding Selections}" >
<ListBox.ItemTemplate>
<DataTemplate>
<Grid >
<CheckBox IsChecked="{Binding IsChecked}"
Content="{Binding Path=Item.SelectionName}" />
</Grid >
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Simplified code behind to allow bindings and INotifyPropertyChanged is:
public ObservableCollection<CheckedListItem<Selection>> Selections { get; set; }
public class Selection
{
public String SelectionName { get; set; }
}
Selections = new ObservableCollection<CheckedListItem<Selection>>();
Selections.Add(new CheckedListItem<Selection>(new Selection()
{ SelectionName = "SomeName" }, isChecked: true));
public class CheckedListItem<T> : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool isChecked;
private T item;
public CheckedListItem()
{ }
public CheckedListItem(T item, bool isChecked = false)
{
this.item = item;
this.isChecked = isChecked;
}
public T Item
{
get { return item; }
set
{
item = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Item"));
}
}
public bool IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("IsChecked"));
}
}
}
I now need to add an additional TextBox associated with each Checkbox, so in XAML I have
<ListBox ScrollViewer.VerticalScrollBarVisibility="Auto"
ItemsSource="{Binding Selections}" Margin="12,22,12,94">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid >
<CheckBox IsChecked="{Binding IsChecked}"
Content="{Binding Path=Item.SelectionName}" />
<<TextBox />
</Grid >
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I'm a bit stumped how to include this as part of the ObservableCollection and set it up the binding on both the CheckBox and associated TextBox? Both are being added together using Selections.Add(new CheckedListItem<Selection>(new Selection()
{ SelectionName = "SomeName" }, isChecked: true)); which is causing me some confusion.
EDIT: Added full code
public partial class SelectionSettingWindow : Window
{
public ObservableCollection<CheckedListItem<Selection>> Selections { get; set; }
public class Selection
{
public String SelectionName { get; set; }
public string SelectionTextField { get; set; }
}
public SelectionSettingWindow()
{
InitializeComponent();
Selections = new ObservableCollection<CheckedListItem<Selection>>();
string fg = #"Item1,true,TExtbox1text:Item2,true,TExtbox2text:Item3,false,TExtbox3text"; //Test String
string[] splitSelections = fg.Split(':');
foreach (string item in splitSelections)
{
string[] spSelectionSetting = item.Split(',');
bool bchecked = bool.Parse(spSelectionSetting[1].ToString());
string tbText = spSelectionSetting[2].ToString();
Selections.Add(new CheckedListItem<Selection>(new Selection()
{ SelectionName = spSelectionSetting[0].ToString(),
SelectionTextField = bText }, isChecked: bchecked));
}
DataContext = this;
}
public class CheckedListItem<T> : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool isChecked;
private T item;
private string textField;
public CheckedListItem()
{ }
public CheckedListItem(T item, bool isChecked = false)
{
this.item = item;
this.isChecked = isChecked;
}
public T Item
{
get { return item; }
set
{
item = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Item"));
}
}
public bool IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("IsChecked"));
}
}
public string TextField
{
get { return textField; }
set
{
textField = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("TextField"));
}
}
}
}
<ListBox ScrollViewer.VerticalScrollBarVisibility="Auto"
ItemsSource="{Binding Selections}" Margin="12,22,12,94">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<CheckBox IsChecked="{Binding IsChecked, Mode=TwoWay}"
Content="{Binding Path=Item.SelectionName}" />
<TextBox Text="{Binding Item.SelectionTextField, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
replace SelectionTextField above with whatever the field is that needs to be edited using the textbox on your Selection class.
Note that I changed the <Grid> to a <StackPanel> So they wouldn't appear on top of eachother and changed the bindings to TwoWay so the changes are reflected in the model.
Make sure your Selection class implements INotifyPropertyChanged (ObservableCollection updates the UI when things get added to/removed from the collection, it doesn't know anything about notifying when it's content's properties change so they need to do that on their own)
Implementing INotifyPropertyChanged on many classes can be cumbersome. I find implementing a base class useful for this. I've got this along with an extra reflection helper for raise property changed available here and a snippet I've made available. It's silverlight but it should work fine for WPF. Using the code I've provided via download you can simply type proprpc and hit tab and visual studio will stub in a property that notifies on change. Some explanation is in one of my old blog posts here and gives credit for where I based the code and snippet from.