Perhaps I don't understand events fully.
I'm building a Windows Phone 7 app in Silverlight.
I have a UserControl that wraps a ListBox, called EditableListBox. The ListBox has a data template. The items in the list box are wrapped by EditableListItem objects.
The data template is as follows:
<DataTemplate>
<Grid ManipulationCompleted="Grid_ManipulationCompleted">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image Source="{Binding Path=IconSource}"
Grid.Column="0"
Width="96"
Height="96"
VerticalAlignment="Center"
Visibility="{Binding Path=Editing, Converter={StaticResource visibilityConverter}}"
/>
<TextBlock Text="{Binding Path=Name}" Grid.Column="1" />
</Grid>
</DataTemplate>
I'm binding the Visibility to a property of each EditableListItem, so I need to implement INotifyPropertyChanged so updates to the backing items are reflected in the UI. (Right? Or is there a simpler way to do it?)
EditableListItem:
public class EditableListItem : INotifyPropertyChanged
{
private EditableListBox _parentListBox;
public event PropertyChangedEventHandler PropertyChanged;
public bool Editing
{
get
{
return _parentListBox.Editing;
}
}
public EditableListItem(Section section, EditableListBox parentListBox)
{
_parentListBox = parentListBox;
// after this line, _parentListBox.PropertyChanged is still null.
// why is that?
_parentListBox.PropertyChanged += PropertyChanged;
_parentListBox.PropertyChanged += new PropertyChangedEventHandler(_parentListBox_PropertyChanged);
}
EditableListBox:
public partial class EditableListBox : UserControl, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
// NotifyPropertyChanged will raise the PropertyChanged event,
// passing the source property that is being updated.
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public void SetSectionsSource(ObservableCollection<Section> sectionsSource)
{
sectionsSource.CollectionChanged += new NotifyCollectionChangedEventHandler(sectionsSource_CollectionChanged);
ContentListBox.ItemsSource = sectionsSource.Select(section => new EditableListItem(section, this) { Enabled = true });
//ContentListBox.ItemsSource.Add(new EditableListItem(new Section("Section", 3)) { Enabled = true });
}
// ...
private bool _editing;
public bool Editing
{
get
{
return _editing;
}
set
{
_editing = value;
NotifyPropertyChanged("Editing");
}
}
}
The Editing property is stored in EditableListBox - EditableListItem just forwards it. I wanted to attached EditableListItem.PropertyChanged to EditableListBox.PropertyChanged directly, but the following didn't work:
// after this line, _parentListBox.PropertyChanged is still null.
// why is that?
_parentListBox.PropertyChanged += PropertyChanged;
The following did work:
_parentListBox.PropertyChanged += new PropertyChangedEventHandler(_parentListBox_PropertyChanged);
Why is this? Is the first attempt totally invalid (if so, why does the compiler allow it?)?
To begin with, you don't wire up the PropertyChanged to implement it. The idea is that WPF uses that event and it wires it up. The only thing you do is trigger the event when applicable.
And that's a part of the issue here. You have the Editing property, but it is not being fired. I do understand that you have wired the PropertyChanged of the parent listbox to get the event to fire, but that is not going to work.
If I get the idea right, what you want to accomplish is when the Editing property of the listbox gets changed, you want the PropertyChanged of the list item to be forced.
One of the things of PropertyChanged is that the sender has to be the object where the PropertyChanged is located. This means that you should implement it like this:
public partial class EditableListBox : UserControl, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
// You really should make this protected. You do not want the outside world
// to be able to fire PropertyChanged events for your class.
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private bool _editing;
public bool Editing
{
get
{
return _editing;
}
set
{
_editing = value;
NotifyPropertyChanged("Editing");
}
}
}
public class EditableListItem : INotifyPropertyChanged
{
private EditableListBox _parentListBox;
public EditableListItem(EditableListBox parentListBox)
{
_parentListBox = parentListBox;
_parentListBox.PropertyChanged += new PropertyChangedEventHandler(_parentListBox_PropertyChanged);
}
void _parentListBox_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
// Forward the event.
if (e.PropertyName == "Editing")
NotifyPropertyChanged("Editing");
}
public event PropertyChangedEventHandler PropertyChanged;
// You really should make this protected. You do not want the outside world
// to be able to fire PropertyChanged events for your class.
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public bool Editing
{
get
{
return _parentListBox.Editing;
}
}
}
I don't know how you get the reference to the editable listbox, but lets say you get it via the constructor. When you get the reference, you attach the the PropertyChanged event handler of the listbox. Because, when the Editing property of that object changes, actually, your Editing property changes too. This is how you simulate that.
One last thing: the reason why the PropertyChanged is still null after the += PropertyChanged is because the PropertyChanged of the object itself is null. You cannot wire the events in this way. The second way is the correct way of wiring up the events, and the above example shows what you do with this.
Related
So I have a c# wpf application with a default layout and different UserControls to fill one part of that layout. So far everything worked like a charm with binding properties, but now that i created another UserControl the binding only seems to work OneWay.
View -> ViewModel works great, I can trace button clicks, comboboxes being checked and all that stuff, but ...
ViewModel -> View doesn't want to work at all.
I've tried setting the Mode of the Bindings to TwoWay and setting UpdateSourceTrigger to PropertyChanged, but nothing changes.
This is my View:
<UserControl ...
xmlns:vm="clr-namespace:Prueftool.BBCCreatorViewModel"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
<vm:CreateDisplayTypeViewModel/>
</UserControl.DataContext>
<Grid>
<Button Content="Button" Width="75" Command="{Binding TestButtonClick}"/>
<CheckBox Content="CheckBox" IsChecked="{Binding TestIsChecked}"/>
</Grid>
</UserControl>
And here is my referenced ViewModel:
namespace Prueftool.BBCCreatorViewModel
{
class CreateDisplayTypeViewModel : ViewModelBase, ICreateDisplayViewModel
{
private bool _testIsChecked;
public bool TestIsChecked
{
get { return _testIsChecked; }
set
{
_testIsChecked = value;
OnPropertyChanged("TestIsChecked");
}
}
public void SetNewDisplayType(DisplayType selectedDisplayType)
{
if(selectedDisplayType.Name == "Default")
{
TestIsChecked = true;
}
}
private DelegateCommand _random;
public ICommand RandomButtonClick
{
get
{
if (_random == null)
{
_random = new DelegateCommand(randomButtonClick);
}
return _random;
}
}
private void randomButtonClick()
{
if(TestIsChecked)
{
MessageBox.Show("Hello World");
}
}
}
}
The SetNewDisplayType method is being called and the if statement is true, but it won't check my combobox in the view. On the other hand, checking the combobox manually and then pressing the button fires the randomButtonClick method and a MessageBox appears.
EDIT:
OnPropertyChanged method (not mine)
#region public virtual void OnPropertyChanged()
/// <summary>
/// Raises this object's PropertyChanged event.
/// </summary>
/// <param name="propertyName">The property that has a new value.</param>
public virtual void OnPropertyChanged(string propertyName)
{
this.VerifyPropertyName(propertyName);
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
#endregion
I think you may be calling SetNewDisplayType on a different instance of CreateDisplayTypeViewModel than the one used as DataContext. The binding works and the checkbox is checked when I use your UserControl and change the Constructor to
public MyUserControl()
{
InitializeComponent();
((CreateDisplayTypeViewModel)DataContext).SetNewDisplayType();
}
and SetNewDisplayType to
public void SetNewDisplayType()
{
TestIsChecked = true;
}
It would help though if you could post how this function is called.
Edit: The fact that the handler in OnPropertyChanged is null (as you mentioned in the comments above) is also a hint that you might be using two instances of the VM.
I think you just need to implement INotifyPropertyChanged on your class.
class CreateDisplayTypeViewModel : ViewModelBase, ICreateDisplayViewModel, INotifyPropertyChanged
I see you have the OnPropertyChanged method but you would also need to implement to PropertyChangedEventHandler. Something like this should do it:
#region Public Events
public event PropertyChangedEventHandler PropertyChanged;
#endregion Public Events
#region Protected Methods
protected void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
#endregion Protected Methods
Binding class provides listening for property paths
Example:
<TextBlock Text="{Binding A.B.C.D.E}" />
Reflects any change in the property chain
I want to utilize this mechanism in code behind.
What I want to do:
var binding = new Binding{
Source = ViewModel,
Path = new PropertyPath("A.B.C.D.E")
};
// not available
binding.ResultChanged += OnBindingResultChanged;
But "Binding" doesn't provide any events
I think u should use the Onpropertychanged Event.
You implement it like this
public class YourClass : window, INotifyPropertyChanged
public string A.B.C.D.E {Get ; set ;}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
after this in your XAML you can raise an event when the property is changed.
<TextBlock Text="{Binding A.B.C.D.E}" textchanged="YOURMETHODENAME"/>
When this methode is called you can change the value of the property in the code behind.
public void YOURMETHODENAME (sender e, routeeventargs)
{
//dostuff with your property
OnPropertyChanged ("A.B.C.D.E)
}
this way you can change the value from the property in the code behind and from user input.
I have a TextBlock control inside a HubSection in a Windows 8.1 Universal app.
<TextBlock x:Name="api_enabled_label"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Text="{Binding APIinfotext}" />
Now when the page is launched, in the contrustor, there is a method that is run.
public string APIinfotext { get; set; }
public sealed partial class MainPage : Page {
VoipMS voip_service = new VoipMS("shoukatali#hotmail.com", "Kitt0cat");
public string APIinfotext { get; set; }
public MainPage() {
this.InitializeComponent();
// disable sections until API is enabled
mainpagehub.Sections[1].IsEnabled = false;
mainpagehub.Sections[2].IsEnabled = false;
//check for API and enable sections
checkAPI();
}
private async void checkAPI() {
//irrelevant code above
switch (result) {
case "success":
APIinfotext = "Your API is connected";
break;
//irrelevant code below
}
}
So why dosnt this work? I set the DataContext of the Textblock to the current class (which is the MainPage partial class) and the property is a public property.
Note: Today is my first time working with .net 4.5 with XAML after a huge break at the .net 2.0 framework with WinForms.
Your binding doesn't know that APIinfotext property has changed. To let the bindings know that the property has changed you can do one of the following. The first one is the easiest.
1) implement INotifyPropertyChanged interface and raise the PropertyChanged changed event once APIinfotext has changed (PropertyChanged("APIinfotext"));
2) Have an event called APIinfotextChanged with the standard event signature and raise that event after the property has changed.
3) Implement your property as a DependencyProperty (not an ideal solution in this case).
You might be missing the part where you have to RaiseProperyChange NotifyPropertyChage to update the bindings. your Model should implement the INotifyPropertyChanged interface
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
then
RaisePropertyChanged("APIinfotext");
http://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged.propertychanged.aspx
Looks like you need a very simple example of what the other two are talking about. Let's assume nothing. You need to set the DataContext correctly, plus raise the event. This is as simple as I can put it, when you click on the button it will change the TextBox because I change the Property which raises the event.
XAML
<Page>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel>
<TextBox Text="{Binding APIinfotext}" Height="100" Width="400" HorizontalAlignment="Left"/>
<Button x:Name="myButton" Content="Change Text" Height="200" Width="400" Click="myButton_Click"/>
</StackPanel>
</Grid>
</Page>
C# (Pay attention, to the SET part of the APIinfotext)
using System.ComponentModel; // INotifyPropertyChanged
public sealed partial class MainPage : Page, INotifyPropertyChanged
{
private string _apiinfotext = "Default Text";
public string APIinfotext
{
get { return _apiinfotext; }
set
{
_apiinfotext = value;
RaisePropertyChanged("APIinfotext");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public MainPage()
{
this.InitializeComponent();
this.DataContext = this;
}
private void myButton_Click(object sender, RoutedEventArgs e)
{
this.APIinfotext = "Don't confuse movement for progress.";
}
}
I'm trying to bind a text block to my variable slctItem. I can see it contain the necessary data I need however my window does not show the data I'm expecting. Here is the code behind for my control. This control is used by a pop up window which will display the values of the control.
When walking the code I see that handler returns null every time in the OnPropertyChanged() method. Why? I must be doing something wrong here. Again slcItemdoes contain the data I'm wanting to use. The OnPropertyChanged() method also fires it just contains null for handler.
public partial class MetaData : UserControl, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _slctItem;
public MetaData()
{
InitializeComponent();
}
public string slctItem
{
get
{
return _slctItem;
}
set
{
_slctItem = value;
OnPropertyChanged("slctItem");
}
}
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
internal void Refresh()
{
try
{
// If DataContext is Null or a detached DataRow, disable the view
if (DataContext != null && (DataContext is DataRow && ((DataRow)DataContext).RowState != System.Data.DataRowState.Detached))
{
if (DataContext is "Something Here")
{
slctItem = (("Something Here")this.DataContext).NAME;
}
}
}
catch (Exception e)
{
throw new Exception("MetaData -> Refresh(): " + e.Message);
}
}
Here is the XAML code for my control. Here I'm trying to bind to slctItem
<TextBox Grid.Column="2" Grid.Row="0" Text="{Binding Path=slctItem, Mode=OneWay, Converter={StaticResource myFirstCharToUpperConverter}}" Width="150" Height="25" HorizontalAlignment="Left" />
You need to set the DataContext to yourself:
public MetaData()
{
InitializeComponent();
this.DataContext = this;
}
This will allow the binding to find the appropriate property. Right now, if you look at the Debug Output in the Output Window at runtime, you should see binding errors since the data context is unset.
I have a WPF ViewModel
class MainWindowViewModel : INotifyPropertyChanged
{
private string _sql;
public string Sql
{
get { return _sql; }
set
{
if (value == _sql) return;
OnPropertyChanged("Sql");
_sql = value;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
I also have a XAML view with a TextBox
<Window.Resources>
<HbmSchemaExporter:MainWindowViewModel x:Key="viewModel"/>
</Window.Resources>
....
<TextBox Grid.Row="6" Grid.Column="0" Grid.ColumnSpan="2" Text="{Binding Source={StaticResource ResourceKey=viewModel}, Path=Sql,Mode=OneWay}"/>
Code behind
private MainWindowViewModel ViewModel
{
get { return Resources["viewModel"] as MainWindowViewModel; }
}
The problem is that when in the code I do viewModel.Sql = SOMETHING the text box doesn't get updated. Debugger displays the correct value in the property but the textbox remains blank.
I also tried to change the binding to TwoWay but that only allows me to overwrite the property with a value I type in the textbox, which is something I don't really want (actually I still need to make it readonly, but it's currently out of scope).
How can I update the textbox after programmatically updating the property?
The application is basically a NHibernate DDL generator I'm writing after reading this. I need to press a "Generate SQL" button and it displays the code to run onto DB.
public string Sql
{
get { return _sql; }
set
{
if (value == _sql) return;
OnPropertyChanged("Sql");
_sql = value;
}
}
That does not make sense. At the point that any PropertyChanged event handler is called, reading Sql will still give the old value, because you haven't updated _sql yet. You need to first update the value, and only then raise the PropertyChanged event.