just need a little help with some databinding.
So I have an ObservableCollection of a custom object. The properties of the object are as shown:
/// <summary>
/// Name
/// </summary>
private string _name;
public string Name
{
get
{
return _name;
}
set
{
if (value != _name)
{
_name = value;
NotifyPropertyChanged("Name");
}
}
}
/// <summary>
/// Status
/// </summary>
private int _status;
public int Status
{
get
{
return _status;
}
set
{
if (value != _status)
{
_status = value;
NotifyPropertyChanged("Status");
}
}
}
/// <summary>
/// Visible information
/// </summary>
private Visibility _visible;
public Visibility Visible
{
get
{
return _visible;
}
set
{
if (value != _visible)
{
_visible = value;
NotifyPropertyChanged("Visible");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
And basically I have three TextBlocks, the first two of which have Text binded to Name and Status while the third has:
Visibility = {Binding Visible}
But whenever I want to toggle the visibility property, I can only toggle it from the Visible state to Collapsed, and then not again. My toggle looks like this:
ItemViewModel l = ((sender as LongListSelector).SelectedItem) as ItemViewModel;
MessageBox.Show(l.Visible.ToString());
if (l.Visible == Visibility.Collapsed)
l.Visible = Visibility.Visible;
else
l.Visible = Visibility.Collapsed;
Note: ItemViewModel is the custom class for the collection, ie.
ObservableCollection<ItemViewModel>
Obviously it's just a rough test, but still doesn't work. The messagebox is always showing "Visible" regardless of the real visibility state of the item. How can I get it to get the real value of Visibility?
If anyone has any idea, that would be great.
Thanks.
EDIT: Adding XAML for the Textblocks for clarity:
<TextBlock Text="{Binding Name}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}" FontSize="20"/>
<TextBlock Text="{Binding Status}" TextWrapping="Wrap" Margin="12,-6,280,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
<TextBlock x:Name="t1" Text="Test for visibility" TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}" Visibility="{Binding Visible}"/>
Keep in mind these textblocks are encased within a stackpanel and a longlistselector so simply changing t1.Visible is out of the question.
The problem, after more investigation, ends up being that the property was being set by another branch of code.
Related
I am currently developing a hamburger style menu in WPF. In this menu, there are some categories that each have an icon. When the menu is collapsed you can still see those icons. When you expand the menu, there should appear text next to it. My idea was to just set their visibility to Visible as soon as the menu opens but I've had a lot of trouble realizing this. Right now I'm trying to change their visibility by binding them to a property.
XAML:
<ListView x:Name="menuItemsListView" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListViewItem>
<StackPanel Orientation="Horizontal">
<Image x:Uid="Test" Name="InhoudImage" Source="Images/noimage.png" Height="30" Width="auto" VerticalAlignment="Center" Margin="3,0,0,0"></Image>
<TextBlock x:Uid="Test" Text="{Binding Path=TextboxVisibility}" Visibility="{Binding Path=TextboxVisibility}" VerticalAlignment="Center"></TextBlock>
</StackPanel>
</ListViewItem>
</ListView>
C# CS Class:
using System.Windows;
using System.Windows.Controls;
namespace APP
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private bool menuOpen = false;
private int closedMenuWidth = 50;
private int openMenuWidth = 210;
private string textboxVisibility;
public string TextboxVisibility
{
get { return textboxVisibility; }
set { textboxVisibility = value; }
}
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
this.TextboxVisibility = "Hidden";
}
private void MenuButton_Click(object sender, RoutedEventArgs e)
{
if (menuOpen)
{
menuGrid.Width = closedMenuWidth;
menuOpen = false;
this.TextboxVisibility = "Hidden";
}
else
{
menuGrid.Width = openMenuWidth;
menuOpen = true;
this.TextboxVisibility = "Visible";
//foreach (ListViewItem item in menuItemsListView.Items)
//{
// item.
// if (item.Uid == "Test")
// {
// item.Visibility = Visibility.Visible;
// }
//}
}
}
}
}
When I change the value within the MainWindow function, it does have an effect on it when it first starts. But the other times I try to change it, which is at runtime, nothing happens. I have tried all sorts of things with booleans and binding the actual Visibility type but nothing worked.
You should implemente INotifyPropertyChanged on your MainWindow class like this:
public partial class MainWindow: Window,INotifyPropertyChanged {
private string textboxVisibility;
public string TextboxVisibility {
get {
return textboxVisibility;
}
set {
textboxVisibility = value;
OnPropertyChanged();
}
}
//The rest of your code goes here
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
PropertyChanged ? .Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
What OnPropertyChanged method does is, whenever the value is setted, it notifies the view and refreshes it.
This will solve the problem but isn't the right way to use MVVM.
The way you should do this is to change the visibility property of the TextBox instead of binding the visibility property to a value:
First you have to add a name to the TextBlock you want to hide:
<ListView x:Name="menuItemsListView" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListViewItem>
<StackPanel Orientation="Horizontal">
<Image x:Uid="Test" Name="InhoudImage" Source="Images/noimage.png" Height="30" Width="auto" VerticalAlignment="Center" Margin="3,0,0,0"></Image>
<TextBlock Name="textblock" x:Uid="Test" Text="{Binding Path=TextboxVisibility}" Visibility="{Binding Path=TextboxVisibility}" VerticalAlignment="Center"></TextBlock>
</StackPanel>
</ListViewItem>
</ListView>
And then you change the visibility in the code
private void MenuButton_Click(object sender, RoutedEventArgs e) {
if (menuOpen) {
menuGrid.Width = closedMenuWidth;
menuOpen = false;
textblock.Visibility = System.Windows.Visibility.Hidden;
}
else {
menuGrid.Width = openMenuWidth;
menuOpen = true;
textblock.Visibility = System.Windows.Visibility.Visible;
//foreach (ListViewItem item in menuItemsListView.Items)
//{
// item.
// if (item.Uid == "Test")
// {
// item.Visibility = Visibility.Visible;
// }
//}
}
}
If you want to implement MVVM the right way you have to create a ViewModel class and add it as Data Context to your view:
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
And then on you MainWindowViewModel is where you change the property:
public class MainWindowViewModel: INotifyPropertyChanged {
private string textboxVisibility;
public string TextboxVisibility {
get {
return textboxVisibility;
}
set {
textboxVisibility = value;
OnPropertyChanged();
}
}
//The rest of your code goes here
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
PropertyChanged ? .Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I have a TextBlock in XAML that's bound to a property called EditsWarning:
<TextBlock DockPanel.Dock="Top" Text="{Binding EditsWarning, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Style="{DynamicResource Esri_TextBlockRegular}" HorizontalAlignment="Left" FontSize="14" FontWeight="DemiBold" VerticalAlignment="Center" Margin="10,0,10,5" TextWrapping="WrapWithOverflow"/>
The Definition for the EditsWarning Property is here:
public string EditsWarning
{
get { return editsWarningMessage; }
set
{
SetProperty(ref editsWarningMessage, value, () => this.EditsWarning);
}
}
The EditsWarning Property is set to an instance of a class like this:
editsWarning = new OutstandingEditsTextBlock();
editsWarningMessage = editsWarning.EditsWarningMessage.ToString();
And the OutstandingEditsTextBlock class is here, and implements INotifyPropertyChanged
internal class OutstandingEditsTextBlock : INotifyPropertyChanged
{
private string editsWarning;
public OutstandingEditsTextBlock()
{
if (Project.Current.HasEdits)
{
this.editsWarning = "This session/version has outstanding edits.";
}
else
{
this.editsWarning = string.Empty;
}
}
public event PropertyChangedEventHandler PropertyChanged;
public string EditsWarningMessage
{
get { return this.editsWarning; }
set
{
this.editsWarning = value;
this.OnPropertyChanged("EditsWarningMessage");
}
}
public void OnPropertyChanged(string propertyName)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
I noticed that I can get it to display either value, however, I can never get it to update in the same debugging session. In fact, it looks like the setter for the public property is never hit.
Can someone please help me figure out what I'm doing wrong?
Thank you.
I have an usercontrol in a UWP who I place in other user controls who have two text TextBlocks who are bound to the VM.
Here is the XAML Code:
DataContext
DataContext="{Binding BalanceView, Source={StaticResource CoreModule}}"
<TextBlock Text="{Binding TotalBalance, Mode=TwoWay, Converter={StaticResource AmountFormatConverter}, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource DeemphasizedBodyTextBlockStyle}"
Margin="0,0,5,0" />
<TextBlock Text=" / "
Style="{StaticResource DeemphasizedBodyTextBlockStyle}"
Margin="0,0,5,0" />
<TextBlock Text="{Binding EndOfMonthBalance, Mode=TwoWay, Converter={StaticResource AmountFormatConverter}, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource DeemphasizedBodyTextBlockStyle}"
Margin="0,0,5,0" />
And the VM Properties there bound to:
public double TotalBalance
{
get { return totalBalance; }
set
{
if (Math.Abs(totalBalance - value) < 0.01) return;
totalBalance = value;
RaisePropertyChanged();
}
}
public double EndOfMonthBalance
{
get { return endOfMonthBalance; }
set
{
if (Math.Abs(endOfMonthBalance - value) < 0.01) return;
endOfMonthBalance = value;
RaisePropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raises this object's PropertyChanged event.
/// </summary>
/// <param name="propertyName">The property that has a new value.</param>
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
I can see that the value returned is correct. But on the UI it's permanently on 0. If I set the value staticly to a value it's shown properly.
What is wrong?
I would suspect you have these double values as private properties which are not shown here, but are referenced such as "totalBalance" vs PUBLIC "TotalBalance" and similar with "endOfMonthBalance" vs "EndOfMonthBalance", otherwise it would not compile.
Also, shouldn't your RaisePropertyChanged() call
RaisePropertyChanged("TotalBalance") and RaisePropertyChanged("EndOfMonthBalance") vs a blanket call via no parameter.
Call OnPropertyChanged passing the name of the CLR property as the paramter.
private double totalBalance=0;
public double TotalBalance
{
get { return totalBalance; }
set
{
if (Math.Abs(totalBalance - value) < 0.01) return;
totalBalance = value;
OnPropertyChanged("TotalBalance");
}
}
private double endOfMonthBalance=0;
public double EndOfMonthBalance
{
get { return endOfMonthBalance; }
set
{
if (Math.Abs(endOfMonthBalance - value) < 0.01) return;
endOfMonthBalance = value;
OnPropertyChanged("EndOfMonthBalance");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
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.
I have a simple WPF app with 3 textboxes, 2 of the text boxes input numbers and the third textbox shows the sum of inputs when another button is clicked.
I come from WinForms and MFC background and for me, the intuitive thing to do is to right click the textBoxes, open their properties and specify local variables to read the data from the boxes. For example, MFC has the DDX mechanism for this.
However, in WPF, the only way to specify a binding seems to add XAML code directly to App.XAML, as shown here on MSDN. Is there a way to create a binding without coding it manually into XAML? XAML coding seems a little daunting to me, since I am new to it.
My WPF form is as follows :
<Window x:Class="SimpleAdd.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>
<TextBox HorizontalAlignment="Left" Height="23" Margin="174,43,0,0" TextWrapping="Wrap" Text="{Binding dataModel.Value1}" VerticalAlignment="Top" Width="120"/>
<TextBox HorizontalAlignment="Left" Height="23" Margin="174,84,0,0" TextWrapping="Wrap" Text="{Binding dataModel.Value2}" VerticalAlignment="Top" Width="120"/>
<TextBox HorizontalAlignment="Left" Height="23" Margin="174,127,0,0" TextWrapping="Wrap" Text="{Binding dataModel.Value3}" VerticalAlignment="Top" Width="120"/>
<Button Content="Add" HorizontalAlignment="Left" Margin="393,84,0,0" VerticalAlignment="Top" Width="75" Click="OnAdd"/>
</Grid>
</Window>
My C# file is as follows :
namespace SimpleAdd
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void OnAdd(object sender, RoutedEventArgs e)
{
dataModel m1 = new dataModel();
m1.Value3 = m1.Value1 + m1.Value2; // BUG : All Properties are 0 even after updating the boxes.
}
}
public class dataModel
{
private int val1, val2, val3;
public int Value1
{
get {return val1;}
set { val1 = value; }
}
public int Value2
{
get { return val2; }
set { val2 = value; }
}
public int Value3
{
get { return val3; }
set { val3 = value; }
}
}
}
EDIT : Adding implementation for INotifyPropertyChanged
namespace SimpleAdd
{
public abstract class ObservableObject : INotifyPropertyChanged
{
#region Debugging Aides
/// <summary>
/// Warns the developer if this object does not have
/// a public property with the specified name. This
/// method does not exist in a Release build.
/// </summary>
[Conditional("DEBUG")]
[DebuggerStepThrough]
public virtual void VerifyPropertyName(string propertyName)
{
// Verify that the property name matches a real,
// public, instance property on this object.
if (TypeDescriptor.GetProperties(this)[propertyName] == null)
{
string msg = "Invalid property name: " + propertyName;
if (this.ThrowOnInvalidPropertyName)
throw new Exception(msg);
else
Debug.Fail(msg);
}
}
/// <summary>
/// Returns whether an exception is thrown, or if a Debug.Fail() is used
/// when an invalid property name is passed to the VerifyPropertyName method.
/// The default value is false, but subclasses used by unit tests might
/// override this property's getter to return true.
/// </summary>
protected virtual bool ThrowOnInvalidPropertyName { get; private set; }
#endregion // Debugging Aides
#region INotifyPropertyChanged Members
/// <summary>
/// Raises the PropertyChange event for the property specified
/// </summary>
/// <param name="propertyName">Property name to update. Is case-sensitive.</param>
public virtual void RaisePropertyChanged(string propertyName)
{
this.VerifyPropertyName(propertyName);
OnPropertyChanged(propertyName);
}
/// <summary>
/// Raised when a property on this object has a new value.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raises this object's PropertyChanged event.
/// </summary>
/// <param name="propertyName">The property that has a new value.</param>
protected virtual void OnPropertyChanged(string propertyName)
{
this.VerifyPropertyName(propertyName);
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
#endregion // INotifyPropertyChanged Members
}
}
Your TextBox are not being updated because you haven't set the data source (DataContext typically) behind the bindings.
When you write
<TextBox Text="{Binding dataModel.Value1}" />
What you are really saying "pull the value for this field from TextBox.DataContext.dataModel.Value1". If TextBox.DataContext is null, then nothing will be displayed.
The DataContext is inherited automatically, so the following code would work :
public partial class MainWindow : Window
{
public dataModel _data { get; set; }
public MainWindow()
{
InitializeComponent();
_data = new dataModel();
this.DataContext = _data;
}
private void OnAdd(object sender, RoutedEventArgs e)
{
_data.Value3 = _data.Value1 + _data.Value2;
}
}
assuming you also change your TextBox bindings to remove the dataModel. from them
<TextBox Text="{Binding Value1}" />
This sets the DataContext of the entire form to the _data object, and in your OnAdd method we can update the _data object properties in order to have the UI update.
I like to blog a bit about beginner WPF stuff, and you may be interested in checking out a couple of the posts there which explain these concepts :
Understanding the change in mindset when switching from WinForms to WPF
What is this "DataContext" you speak of?
Technically, that's not in App.xaml (which is a special file in WPF).
That said, yes you can do it. You can set up the binding in code like so:
textBox1.Text = new Binding("SomeProperty");
...
Okay, that gets really annoying so we just do it in XAML:
<TextBox Text="{Binding SomeProperty}"/>
Both pieces of code do the same thing, but when you get into more advanced bindings, the XAML syntax is a lot easier to use. Plus, its more obvious where you text is coming from rather than having to open two files.
The FrameworkElement class and the FrameworkContentElement class both expose a SetBinding method. If you are binding an element that inherits either of these classes, you can call the SetBinding method directly.
The following example creates a class named, MyData, which contains a property named MyDataProperty.
public class MyData : INotifyPropertyChanged
{
private string myDataProperty;
public MyData() { }
public MyData(DateTime dateTime)
{
myDataProperty = "Last bound time was " + dateTime.ToLongTimeString();
}
public String MyDataProperty
{
get { return myDataProperty; }
set
{
myDataProperty = value;
OnPropertyChanged("MyDataProperty");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string info)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(info));
}
}
}
The following example shows how to create a binding object to set the source of the binding. The example uses SetBinding to bind the Text property of myText, which is a TextBlock control, to MyDataProperty.
MyData myDataObject = new MyData(DateTime.Now);
Binding myBinding = new Binding("MyDataProperty");
myBinding.Source = myDataObject;
myText.SetBinding(TextBlock.TextProperty, myBinding);