I'm trying out WPF and I have an application with an ObservableCollection hooked up to a ListView. It adds the items perfectly and everything is falling together. It is taking in data from a live source and so item values are getting updated and rows are getting added. But now I want the most recent rows to be highlighted, text changed, something to show that this certain row is being changed. So I set up a data trigger and added a value to the DataType in the ObservableCollection called RecentlyChanged. If its true, then set the text to red, otherwise text is black.
When I run this code the list items change to red and then never change back and I have tried everything and it is ticking me off. I've checked the debugger to and even when the value is no (I'm using strings yes and no because i wanted to try all sorts of data types) it stays red. Code for the data trigger is below:
--Edit: Added in the second datatrigger that i tried using before to no avail.
<ListView ItemsSource="{Binding DataTable, UpdateSourceTrigger=PropertyChanged}">
<ListView.Resources>
<Style TargetType="ListViewItem">
<Setter Property="Foreground" Value="Black"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=RecentlyChanged}" Value="yes">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=RecentlyChanged}" Value="no">
<Setter Property="Foreground" Value="Black" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.Resources>
The issue is that the ObservableCollection only reports changes to the collection itself, in other words, it fires a CollectionChanged event whenever items are added or removed, but not when properties of those items change. In order to achieve the desired result - updating a data trigger when an item's property changes - the item itself must implement the INotifyPropertyChanged interface and fire the PropertyChanged event when the desired property is set.
In this case, you can use the following:
using System.ComponentModel;
public class ListViewItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
var propChanged = PropertyChanged;
if(propChanged != null)
{
propChanged(this, new PropertyChangedEventArgs(name));
}
}
private string recentlyChanged = "yes"; // Recently changed on creation
public string RecentlyChanged
{
get { return recentlyChanged; }
set {
recentlyChanged = value;
OnPropertyChanged("RecentlyChanged");
}
}
// ... define the rest of the class as usual
}
WPF magic should take care of the rest.
Related
As I'm new in C# WPF, I tried to give it a go, using the trial-and-error approach.
What I would like to do, is to change the appearance of a row in a DataGrid, based on some user action.
However, from reading information over the internet, I've understood that the "right" way to go is:
You should not write a method or a function: you need to write an event.
Okay, okay, so I started doing this in the XAML:
<DataGrid x:Name="dg_Areas" ... LoadingRow="View_dg_Areas"/>
Apparently, the View_dg_Areas event handler should have following signature:
private void View_dg_Areas(object sender, DataGridRowEventArgs e) { }
But then what?
I started, very naïvely, as follows:
private void View_dg_Areas(object sender, DataGridRowEventArgs e)
{
System.Diagnostics.Debug.WriteLine(e.ToString());
System.Diagnostics.Debug.WriteLine(e.Row.ToString());
System.Diagnostics.Debug.WriteLine(e.Row.Item.ToString());
}
The idea was to learn from this how I can find information of the corresponding row (is there a way to read the value of a certain column?), but I didn't get anywhere.
I can tell you that the DataGrid is linked to a DataTable, as follows:
dg_Areas.ItemsSource = dataSet.Tables["Areas"].DefaultView;
How can I link the event parameter e and the DataTable the DataGrid is representing?
You could use a RowStyle with a DataTrigger that binds to one of your properties or columns:
<DataGrid x:Name="dg_Areas">
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding YourColumn}" Value="SomeValue">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
</DataGrid>
The above sample markup changes the background colour of the rows where "YourColumn" equals "SomeValue".
Using XAML defined styles to change the visual appearance is considered a best practice. Handling events is not.
I am struggling to wrap my head around the real benefit of binding in WPF.
I have an application with a large textbox, designed for taking several hundred characters of user input. I have bound this to a "Text" string in my ViewModel. This works OK.
I also have a button with content "Submit". I need to change the content of this button once or twice, so I am doing it in the click event method in the window's code behind. I could, of course, bind the text to the ViewModel, but is it really worth it?
Should everything have a binding? What if I need to display a MessageBox? That will need some logic inside the onclick.
Should click events me as follows:
private void button_Login_Click(object sender, RoutedEventArgs e)
{
viewModel.DoSomething();
}
..where everything gets handed to the ViewModel?
I know this is a general question but I have tried my best to ask direct, answerable questions.
UI concerns are perfectly fine residing in your codebehind.
Business concerns should reside in your ViewModels. The commands and information they expose are what should be bound to elements in your UI.
Since changing the text in a button based on what the button is supposed to do is a UI concern, binding the text of the button to your ViewModel would be pointless.
I wouldn't put any code in codebehind. Create an ICommand property in your ViewModel and bind the buttons Command property to it. I use the ICommand implementation from MVVM Light (RelayCommand) but you can create your own or use one of the many other frameworks.
I'd then have a State property (ProcessStatus here) that I use a DataTrigger with to update the text on my button.
ViewModel
public ICommand LoginCommand
{
get
{
return new RelayCommand(() =>
{
ProcessStatus = Status.AUTHORIZING;
DoSomething();
});
}
}
private Status _processStatus;
public Status ProcessStatus
{
get { return _processStatus; }
set
{
if (value ==_processStatus)
return;
_processStatus= value;
RaisePropertyChanged("ProcessStatus");
}
}
View
<Button Command="{Binding LoginCommand}">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Content"
Value="Submit" />
<Setter Property="IsEnabled"
Value="True" />
<Style.Triggers>
<DataTrigger Binding="{Binding ProcessStatus}"
Value="{x:Static enum:Status.AUTHORIZING}">
<Setter Property="Content"
Value="Authorizing..." />
<Setter Property="IsEnabled"
Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
Before I start explaining my issue, please note that my target framework is .NET 3.5.
I have a textbox whose text is bound to a viewmodel property. My requirement is that when user enters something(via keyboard as well as Mouse Paste) into the textbox, any junk characters inside it should be cleaned and the textbox should be updated with the replaced string[In the below example 's' to be replaced with 'h'].
XAMLCode:
<Style x:Key="longTextField" TargetType="{x:Type TextBoxBase}">
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="AcceptsReturn" Value="True"/>
<Setter Property="AllowDrop" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBoxBase}">
<Border
Name="Border"
Padding="2"
Background="Transparent"
BorderBrush="LightGray"
BorderThickness="1">
<ScrollViewer Margin="0" x:Name="PART_ContentHost"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Foreground" Value="Black"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<TextBox Grid.Column="1" Grid.Row="2" Text="{Binding Value, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, NotifyOnSourceUpdated=True, NotifyOnTargetUpdated=True}" MinLines="3" TextWrapping="Wrap"
SpellCheck.IsEnabled="True" Style="{StaticResource longTextField}"></TextBox>
ViewModel property:
private string _value;
public string Value
{
get
{
return _value;
}
set
{
if (_value == value)
return;
_value = value;
//replaces 's' with 'h' and should update the textbox.
_value = _value.Replace('s','h');
RaisePropertyChanged(() => Value);
}
}
The above is simply not working for me. The view model property setter is firing...the value is getting replaced..however the textbox is not getting updated. What is confusing is that this works perfectly on .Net4.0.
Do you know why this wont work and what is a potential solution to this problem, of course other than upgrading to .NET 4.0?
My requirement:
User can type as well as paste anything into a multilined textbox.
The text can contain junk which should be changed before it comes on to the textbox.
Thanks in advance,
-Mike
I encountered a very similar problem where I wanted the two way binding and I was modifying the value in the ViewModel and expecting to see the update in the TextBox. I was able to get it resolved. Although I was using .NET 4.0, I basically had the same issue, so this may be worth trying out for your situation with 3.5 as well.
Short Answer:
What I encountered was a bug where the TextBox's displayed text was getting out of sync with the value of that TextBox's own Text property. Meleak's answer to a similar question clued me in to this and I was able to verify this with the debugger in Visual Studio 2010, as well as by employing Meleak's TextBlock technique.
I was able to resolve it by using explicit binding. This required handling the UpdateSource() and UpdateTarget() issues myself in code behind (or in a custom control code as I eventually did to make it easier to reuse).
Further Explanation:
Here's how I handled the explicit binding tasks. First, I had an event handler for the TextChanged event which updated the source of the binding:
// Push the text in the textbox to the bound property in the ViewModel
textBox.GetBindingExpression(TextBox.TextProperty).UpdateSource();
Second, in I had an event handler for the TextBox's Loaded event. In that handler, I registered a handler for the PropertyChanged event of my ViewModel (the ViewModel was the "DataContext" here):
private void ExplicitBindingTextBox_Loaded(object sender, RoutedEventArgs e)
{
TextBox textBox = sender as TextBox;
if (textBox.DataContext as INotifyPropertyChanged == null)
throw new InvalidOperationException("...");
(textBox.DataContext as INotifyPropertyChanged).PropertyChanged +=
new PropertyChangedEventHandler(ViewModel_PropertyChanged);
}
Finally, in the PropertyChanged handler, I cause the TextBox to grab the value from the ViewModel (by initiating the UpdateTarget()). This makes the TextBox get the modified string from the ViewModel (in your case the one with replaced characters). In my case I also had to handle restoring the user's caret position after refreshing the text (from the UpdateTarget()). That part may or may not apply to your situation though.
/// <summary>
/// Update the textbox text with the value that is in the VM.
/// </summary>
void ViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
// This textbox only cares about the property it is bound to
if (e.PropertyName != MyViewModel.ValueStrPropertyName)
return;
// "this" here refers to the actual textbox since I'm in a custom control
// that derives from TextBox
BindingExpression bindingExp = this.GetBindingExpression(TextBox.TextProperty);
// the version that the ViewModel has (a potentially modified version of the user's string)
String viewModelValueStr;
viewModelValueStr = (bindingExp.DataItem as MyViewModel).ValueStr;
if (viewModelValueStr != this.Text)
{
// Store the user's old caret position (relative to the end of the str) so we can restore it
// after updating the text from the ViewModel's corresponding property.
int oldCaretFromEnd = this.Text.Length - this.CaretIndex;
// Make the TextBox's Text get the updated value from the ViewModel
this.GetBindingExpression(TextBox.TextProperty).UpdateTarget();
// Restore the user's caret index (relative to the end of the str)
this.CaretIndex = this.Text.Length - oldCaretFromEnd;
}
}
I am fairly new to WPF but have spent time researching WPF validation, and have not yet seen a good approach to conditional validation.
To simplify the situation greatly, let's say I have two textboxes and a submit button. The user enters a string in the first textbox. If the user enters, for example "ABC", then the second textbox should be a required field (I'd want the background to be a light blue color, to signify this), and the submit button should be disabled until that textbox is populated.
How can this be done? Is there an easy way to add/remove validations in runtime? 'DataAnnotations' (http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.aspx) seemed like a good starting place, however I can't mark a field with the [Required] attribute, as the field won't always be required. Basically, I need something like 'Required if Field1 = 'ABC'
Thanks!
I would handle it using MVVM and here is a sample for that.
Implement IDataError Info on the class and that will implement two properties Error and this[string columnName] you can implement the second property with your binding errors that you want
public class MainViewModel:ViewModelBase,IDataErrorInfo
{
public string Error
{
}
public string this[string columnName]
{
get
{
string msg=nulll;
switch(columnName)
{
case "MyProperty": //that will be your binding property
//choose your validation logic
if(MyProperty==0||MyProperty==null)
msg="My Property is required";
break;
}
return msg;
}
}
Also Set ValidateOnErrors=True in binding of a textbox. here ColumnName is the name of the property that is changed and that has ValidateOnErrors set to true. Check here and put up the conditions and return message then you will see the errors on the tooltip when you put this style in your Resources.
<UserControl.Resources>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true" >
<Setter Property="Foreground" Value="Red"/>
<Setter Property="Background" Value="MistyRose"/>
<Setter Property="BorderBrush" Value="Red"/>
<Setter Property="BorderThickness" Value="1.0"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self},Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
and here is a sample of the textbox
<TextBox Text="{Binding UpdateSourceTrigger=PropertyChanged, Mode=TwoWay,
Path=PropertyName,ValidatesOnDataErrors=True}" Name="textBox1">
<Validation.ErrorTemplate>
<ControlTemplate>
</ControlTemplate>
</Validation.ErrorTemplate>
</TextBox>
I would just handle this logic in your ViewModel (assuming you're using an MVVM pattern, if not just in your code-behind).
Fire some logic on the TextChanged event for the first textbox that ultimately sets the appropriate properties. Essentially I'm saying code this validation manually. Once you start getting into more complex validation logic like this your going to start running into the limitations of the validation frameworks / declarative validation.
I have a very simple object called CellData. Is defined as:
public sealed class CellData
{
internal string DisplayText
{
get;
set;
}
public string Color
{
get;
set;
}
public override string ToString()
{
return this.DisplayText;
}
}
I can get it to display using the WPF toolkit DataGrid just fine. However, I want to be able to change the background color of each cell based on what data is in the cell. I'm having trouble understanding what type of binding I need to do because I can't see to get to the CellData object in my DataTrigger. I have tried the following, and several other variations but I can't get it to work:
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=(CellData).Color, Mode=OneWay}" Value="1">
<Setter Property="Background" Value="Red" />
<Setter Property="Foreground" Value="White" />
</DataTrigger>
I am pretty new to XAML databidinding so any suggestions would be greatly appreciated. Thank you.
I'm guessing you have a RowData object that contains several CellData objects, that you have bound the ItemsSource of the DataGrid to a list of RowData objects, and that you are using DataGridTextColumns or other DataGridBoundColumns bound to the properties on RowData, maybe just by using AutoGenerateColumns="True".
The problem is that the DataContext for the cell is actually the RowData, not the CellData. The Binding is only used for the Text property of the TextBlock and TextBox. This is useful if you want to have the triggers based on other properties of the RowData object, but makes it difficult in scenarios like yours where you have a rich data structure for the cell data.
If you are creating the columns explicitly, you can just use the property for the column again in the trigger:
<DataGridTextColumn Binding="{Binding Foo}">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Style.Triggers>
<!-- DataContext is the row, so start
binding path with Foo property -->
<DataTrigger Binding="{Binding Foo.Color}" Value="1">
<Setter Property="Background" Value="Red" />
<Setter Property="Foreground" Value="White" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
Unfortunately, this won't let you share the style between columns since it is specific to the column name. If you want to do that, you may need to create a custom DataGridColumn subclass that has a property of type Binding that it applies to the DataContext property of the object returned by GenerateElement and GenerateEditingElement.
(Using a Binding with RelativeSource of Self as you did in your sample gives you the element in the visual tree rather than its DataContext, which won't help you get to the CellData object.)
You can use a ValueConverter: create a binding between the cell's background color and the Color property of your object, then add a ValueConverter to ensure that your data is properly converter to the object needed to set the background.