I have an ObservableCollection containing Hour (object). Inside, I have a Title and a Value properties.
On my view, I have a listview, binding on this collection. Title is a textblock, Value is a textbox (user can input text).
I would like to change the content of all textbox (value) when one change.
A litle bit of code :
public class Hour : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void NotifyPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
public string Title { get; set; }
private int valueContent;
public int Value
{
get { return valueContent; }
set
{
valueContent = value;
NotifyPropertyChanged("Value");
}
}
}
my observablecollection :
private ObservableCollection<Hour> hours;
public ObservableCollection<Hour> Hours
{
get { return hours; }
set
{
hours= value;
NotifyPropertyChanged("Hours");
}
}
xaml :
<ListBox Grid.Column="1" Grid.ColumnSpan="3" Grid.Row="3" Grid.RowSpan="3" ItemsSource="{Binding Hours, Mode=TwoWay}" SelectedItem="{Binding SelectedHour,Mode=TwoWay}" ItemTemplate="{StaticResource HourTemplate}" />
<DataTemplate x:Key="HourTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Title}" FontSize="18" Width="150" />
<TextBox Text="{Binding Value, Mode=TwoWay}" FontSize="15" Width="150" TextChanged="TextBox_TextChanged" />
</StackPanel>
</DataTemplate>
So, I will have for example :
Title - Value
08h00 - 0
09h00 - 0
10h00 - 0
11h00 - 0
12h00 - 0
I would like, when I change one value (e.g: 10h00), all value after this one change to the value of 10h00.
Here the result expected :
Title - Value
08h00 - 0
09h00 - 0
10h00 - 1 <--- change here
11h00 - 1 <--- change because 10h00 changed
12h00 - 1 <--- change because 10h00 changed
Thanks for your help.
There isn't any clean way to do this.
I would start by adding an event to the Hour class, ValueUpdated. Raise that event in the setter for Value and have the view model listen to it for every Hour object. Have the event pass the sender as a parameter, something like:
public event Action<Hour> ValueUpdated;
//When raising
var handler = ValueUpdated;
if (handler != null)
handler(this);
Now in the view model handler, you need to find the index of the sender, then apply the change to every hour after it.
private void HandleValueUpdate(Hour sender)
{
int senderIndex = allItems.IndexOf(sender);
IEnumerable<Hour> subsequentHours = allItems.Skip(senderIndex + 1);
foreach (Hour h in subsequentHours)
{
h.SetValue(sender.Value);
}
}
You'll probably want to make a way of doing that set without raising the ValueUpdated event, as this won't be very efficent if you do. I modeled that by calling a function instead of setting the property, but how you do it is up to you.
Related
I am trying to learn WPF by implementing a simple button and textbox. I want to understand why my buttons IsEnabled state isn't updating based on the value of my text field.
XAML:
<TextBox Height="100"
TextWrapping="Wrap"
Text="{Binding Test,NotifyOnSourceUpdated=True,NotifyOnTargetUpdated=True}"
VerticalAlignment="Top"
Padding="5, 3, 1, 1"
AcceptsReturn="True" Margin="161,10,10,0"/>
<Button Content="Go"
IsEnabled="{Binding MyButtonCanExecute}"
Command="{Binding MyButtomCommand}"
HorizontalAlignment="Left"
Margin="64,158,0,0"
VerticalAlignment="Top" Width="75"/>
C#:
class MainWindowViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public bool MyButtonCanExecute
{
get
{
return !String.IsNullOrWhiteSpace(Test);
}
}
private ICommand myButtonCommand;
public ICommand MyButtomCommand
{
get
{
if(myButtonCommand == null)
{
myButtonCommand = new RelayCommand(ShowMessage, param => this.MyButtonCanExecute);
}
return myButtonCommand;
}
}
private string test;
public string Test
{
get { return this.test; }
set
{
if (this.test != value)
{
this.test = value;
this.NotifyPropertyChanged("Test");
}
}
}
public MainWindowViewModel()
{
//
}
public void ShowMessage(object obj)
{
MessageBox.Show("Value of textbox is set to: " + this.Test);
}
public void NotifyPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
Questions:
When I type into the textbox, my breakpoint in the Test setter does not get hit. Why? If the textbox is bound to the Test property, isn't that the point?
When I type into the textbox, the MyButtonCanExecute gets called constantly. However, in debug the value of test is always null... why? Shouldn't it take whatever I type into the textbox?
The main issue seems to be that the value of Test isn't updating whenever I type.
I understand there may be a different way to implementing binding the IsEnabled state to the length of test, but I want to understand what's wrong with my understanding of how WPF works.
Answering my own question for future readers. Thanks for #ASh for pointing me in the right direction.
The problem was that, when typing into the textbox, the UpdateSourceTrigger had its default value (because I hadn't set it in the XAML). This meant that the property the textbox was bound to didn't update until the element lost focus, rather than when its text changed.
The solution:
Change the Text field of the textbox to this:
Text="{Binding Test,UpdateSourceTrigger=PropertyChanged}"
Note the new UpdateSourceTrigger=PropertyChanged means the source property (MainWindowViewModel.Test) updates every time I type.
Then, inside the Test property setting I had to add a new NotifyPropertyChanged call for the MyButtonCanExecute property which is dependent on the value of Test:
private string test;
public string Test
{
get { return this.test; }
set
{
if (this.test != value)
{
this.test = value;
this.NotifyPropertyChanged("Test");
this.NotifyPropertyChanged("MyButtonCanExecute");
}
}
}
And also add the UpdateSourceTrigger to the IsEnabled value of the button:
<Button Content="Go"
IsEnabled="{Binding MyButtonCanExecute,UpdateSourceTrigger=PropertyChanged}"
Command="{Binding MyButtomCommand}" />
EDIT:
A better solution is to remove the IsEnabled binding altogether, so you just have:
<Button Content="Go"
Command="{Binding MyButtomCommand}" />
This is because when we create the MyButtonCommand we pass in MyButtonCanExecute as the CanExecute property to the RelayCommand:
myButtonCommand = new RelayCommand(ShowMessage, param => this.MyButtonCanExecute);
I have a textbox ..i want when user put any value in textbox i wan to validate the entered value on LostFocus..
Currently What is happening now my property is updated but my UI is not updated at same time..i amusing below code for that:
<local:Customdatepicker x:Uid="dateValue" x:Name="dateValue" BorderThickness="0"
Visibility="{Binding ShowDatePicker ,UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding FieldValueIsEditable,UpdateSourceTrigger=PropertyChanged}"
DateCoordinates="{Binding CoOrdinates,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"
Text="{Binding DateFieldValue,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, TargetNullValue=''}"
Loaded="dateValue_Loaded" LostFocus="dateValue_LostFocus"
Style="{StaticResource DatePickerStyle}"
Width="{Binding DatePickerWidth,UpdateSourceTrigger=PropertyChanged}"
MouseDoubleClick="dateValue_MouseDoubleClick"
DisplayDate="{Binding DisplayDateFieldValue, Mode=TwoWay}"
>
</local:Customdatepicker>
I have tried below two ways
In first have taken "UpdateSourceTrigger=PropertyChanged" but in this case my UI is not updated
Text="{Binding DateFieldValue,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, TargetNullValue=''}"
In Second case have taken "UpdateSourceTrigger=LostFOcus" but in this case i didn't get the updated value of textbox.
So how can i Achieve both at one time i.e the updated value in UI.
Also i have use INotifyPropertyChanged ALready this is also not wokring.Please help me how can i solve this.
ViewModel:
private string _datefieldvalue;
public string DateFieldValue
{
get { return _datefieldvalue; }
set
{
value = value == null ? string.Empty : value.Trim();
if (string.IsNullOrEmpty(value))
{
_datefieldvalue = ValidateDateValue("");
FirePropertyChanged();
}
else if (!_datefieldvalue.Equals(value, StringComparison.CurrentCultureIgnoreCase))
{
_datefieldvalue = ValidateDateValue(value);
FirePropertyChanged();
}
}
}
public class BaseObservableObject : INotifyPropertyChanged
{
#region PropertyChangedEventHandler
public event PropertyChangedEventHandler PropertyChanged;
protected void FirePropertyChanged([CallerMemberName] string propName = null)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propName));
}
}
#endregion
}
I am trying to determine which of my Sliders Invoked the Event, so I can call the OutputAnalogChannel Method with the Index of the Slider and the Slider value.
My Sliders that could potentially invoke the Event are called:
{ K8055AnalogOutputSlider1, K8055AnalogOutputSlider2, [...], K8055AnalogOutputSlidern }
So nothing is wrong with the following code, it works, but I feel like this is a very 'bad' way of solving this problem.
What i was thinking is that some kind of 'additional' integer value is added to the Slider which corresponds to the correct Slider at the Index.
Honestly this answer is probably hiding somewhere on stackoverflow, but I am not sure what I'd be searching for, so i posted here. Thanks in advance!
private void K8055AnalogOutputSliderValueChanged(object sender, RoutedEventArgs e)
{
Slider slider = sender as Slider;
K8055.OutputAnalogChannel(int.Parse(slider.Name[slider.Name.Length - 1].ToString()), (int)slider.Value);
}
You could use the controls' Tag property. Just set the property to the index of the control and then check it in your event handler:
K8055.OutputAnalogChannel((int)slider.Tag, (int)slider.Value);
This is a little more work, but it makes things incredibly easy to modify and maintain and read. It also gets you started taking advantage of some very powerful features of WPF. But if you're under severe deadline pressure, Vincent's quick fix has the virtue of simplicity.
C#
public class ChannelViewModel : INotifyPropertyChanged
{
private string _name = "";
public string Name
{
get { return _name; }
set
{
_name = value;
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(Name)));
}
}
private int _channel = 0;
public int Channel
{
get { return _channel; }
set
{
_channel = value;
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(Channel)));
}
}
private int _value = 0;
public int Value
{
get { return _value; }
set
{
_value = value;
K8055.OutputAnalogChannel(Channel, Value);
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(Value)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
Channels.Add(new ChannelViewModel { Name="Fred", Channel = 1, Value = 3 });
Channels.Add(new ChannelViewModel { Name="Bob", Channel = 2, Value = 35 });
}
public ObservableCollection<ChannelViewModel> Channels { get; private set; }
= new ObservableCollection<ChannelViewModel>();
public event PropertyChangedEventHandler PropertyChanged;
}
XAML
<ItemsControl
ItemsSource="{Binding Channels}"
BorderBrush="Black"
BorderThickness="1"
>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="2">
<TextBlock>Channel
<Run Text="{Binding Channel, Mode=OneWay}" />:
<Run Text="{Binding Name, Mode=OneWay}" /></TextBlock>
<Slider Value="{Binding Value}" Minimum="1" Maximum="100" Width="300" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
My Xaml
<TextBox Text="{Binding MyVal, Mode=TwoWay}" ></TextBox>
My Viewmodel
private string myVar;
public string MyVal
{
get
{
return myVar;
}
set
{
if (value.Length > 6)
myVar = value;
else
myVar = "Not a valid INPUT";
OnPropertyChanged("MyVal");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
when ever user enters a less than 6 char string the textbox should disply error message. instead of that the textbox text is remains same as the user input. But the variable value is changing as expected.
I'm using WinRT app please help Thanks In advance.
I would change your xaml code this way :
<TextBox Text="{Binding MyVal, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" ></TextBox>
Now every time your property will change, the view will be notified.
and if the UI is still not updated try adding IsAsync=true :
<TextBox Text="{Binding MyVal, Mode=TwoWay, IsAsync=true}"></TextBox>
Your example easily works when I use it in WPF app.
Rembember that the trigger fires when focus is lost on the control.
I've added second textbox, so when you change focus on it, the first TextBox with binding will fire the event.
View
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
this.DataContext = new ViewModel();
InitializeComponent();
}
}
}
View (XAML)
<Window x:Class="WpfApplication1.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="223,173,0,0" TextWrapping="Wrap" Text="{Binding MyVal, Mode=TwoWay}" VerticalAlignment="Top" Width="120"/>
<TextBox HorizontalAlignment="Left" Height="23" Margin="281,252,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/>
</Grid>
</Window>
ViewModel
namespace WpfApplication1
{
class ViewModel : INotifyPropertyChanged
{
private string myVar;
public string MyVal
{
get
{
return myVar;
}
set
{
if (value.Length > 6)
myVar = value;
else
myVar = "Not a valid INPUT";
NotifyPropertyChanged("MyVal");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public ViewModel() { }
}
}
The problem is that you are changing the value in the setter, and then firing the INPC event directly. However, since the value is being currently changed anyway, the TextBox ignores the event. See also this question.
This exact implementation of this was changed several times in .NET 3.5/4.0/4.5, so it currently (4.5) works as you expect (but has other side effects, ex. when binding to a double).
The easiest solution for you would be firing the INPC event with a slight delay, which means the TextBox is forced to read the (possibly updated) value again:
Dispatcher.CurrentDispatcher.BeginInvoke(NotifyPropertyChanged("MyVal"));
I'm trying to display the number of records retrieved by the query after the window loads. Here's what I have in my XAML:
<TextBlock Name="numRecordsAnalyzed_TAtab" TextWrapping="Wrap" Margin="12,0,0,4" Grid.RowSpan="2">
<Run Text="Records Found: " Foreground="{StaticResource Foreground}" FontSize="12"/>
<Run Text="{Binding Numrecords}" Foreground="Red" FontSize="12"/>
</TextBlock>
Here's my c#:
private int numOfrecords = 0;
public event PropertyChangedEventHandler PropertyChanged;
public string Numrecords
{
get { return Convert.ToString(numOfrecords); }
set
{
OnPropertyChanged("NumOfrecords");
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
Then I add this to get the number of records and when I debug I see that the variable holds the number and everything but nothing is displayed in the window when the window launches:
numOfrecords = OpenTradesQuery.Count();
What am I missing?
You need to raise PropertyChanged event to notify GUI to update.
Declare property of type int, WPF will automaically call ToString() on your property, you need not to worry about that.
public int Numrecords
{
get { return numOfrecords; }
set
{
if(numOfrecords != value)
{
numOfrecords = value;
OnPropertyChanged("Numrecords");
}
}
}
Set the property:
Numrecords = penTradesQuery.Count();
You can set DataContext in code behind after InitializeComponent() in constructor of Window/UserControl:
DataContext = this;
Also, you can set it in XAML at root level like this:
<Window DataContext="{Binding RelativeSource={RelativeSource Self}}"/>