I am trying to highlight an item in the list view on a particular condition.I have Made Highlight property in the code behind which is making the decision and it is bind to a datatrigger.
The problem is that When one item in the list is changed, data trigger is updating all the item in the list view
Data trigger in the xaml
<DataTrigger Value="True" Binding="{Binding ElememtName=UserControl, Path=Highlighted}">
<Setter Property="Background" Value="Salmon"/>
</DataTrigger>
Property in the code behind
public bool Highlighted
{
get
{
return this.highlighted;
}
set
{
if (value != this.highlighted)
{
this.highlighted = value;
NotifyPropertyChange("Highlighted");
}
}
}
Related
I want to get some selected rows items & try to manipulate them. Currently SelectedItem is giving me only one row at a time. And SelectedItems is not a dependency property. I found a solution by creating our own dependency property to get selected items. Is there any option apart from this?
Another possible solution is to add an IsSelected property onto the items your showing in your grid
public bool IsSelected
{
get { return _isSelected; }
set
{
RaisePropertyChanged(_isSelected, value);
}
}
and to then add a style onto the data grid row to change that property.
<Style TargetType="{x:Type DataGridRow}" >
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}" />
</Style>
Then to get the currently selected items:
var selectedItems = Items.Where(i => i.IsSelected).ToList();
I'm using an MVVM approach. I've got a datagrid with columns for the days of the week. I ned to highlight the current day with a shaded background. Everything else in the viewmodel is displaying properly so it's binding in general. However, the shading is never applied and if I put breakpoints in my properties, the breakpoints are never hit. I'm doing something stupid but can't spot what.
Here's my code for the colours in the viewmodel:
public Brush SundayColor { get { return GetBrushColorForWeekday(DayOfWeek.Sunday); } }
public Brush MondayColor { get { return GetBrushColorForWeekday(DayOfWeek.Monday); } }
public Brush TuesdayColor { get { return GetBrushColorForWeekday(DayOfWeek.Tuesday); } }
public Brush WednesdayColor { get { return GetBrushColorForWeekday(DayOfWeek.Wednesday); } }
public Brush ThursdayColor { get { return GetBrushColorForWeekday(DayOfWeek.Thursday); } }
public Brush FridayColor { get { return GetBrushColorForWeekday(DayOfWeek.Friday); } }
public Brush SaturdayColor { get { return GetBrushColorForWeekday(DayOfWeek.Saturday); } }
private Brush GetBrushColorForWeekday(DayOfWeek dayOfWeek)
{
return dayOfWeek == CurrentDate.DayOfWeek ? Brushes.AliceBlue : Brushes.White;
}
For the grid XAML I'm using the following. To make it brief I've shown one column definition only but the other six are similar:
<DataGridTextColumn Header="Mon" Width="33" Binding="{Binding MondayQuantity,NotifyOnTargetUpdated=True}">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="Background" Value="{Binding MondayColor}"/>
<Setter Property="Foreground" Value="Black"/>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
I know the styles work as if I change the binding to a fixed colour I see the chosen colour. So why aren't my bindings working?
EDIT
I was being stupid! The colour properties are in my viewmodel directly, not in the items that the grid is bound to.
Is it possible to bind the grid styles to items from the viewmodel that are outside of the observablecolleciton the grid is bound to? I suppose, "one level up" is what I'm after:
viewmodel
mondaycolor, etc. <-- bind style to this
items observablecollection <-- data in grid is from this
quantities
I'm used to using converters so... I'm thinking about something like that (psoeudo-code)
Xaml:
<Setter Property="Background" Value="{Binding Parameter="Monday",
Converter={StaticResource currentDayToColorCOnverter}}"/>
Converter:
class CurrentyDayToColorConverter : IValueConverter
{
public object Convert(blablabla, .., object parameter)
{
if (CurrentDate.DayOfWeek == parameter) // may not be exactly that but you get the idea
{
return Brushes.AliceBlue;
}
else
{
return Brushes.White
}
}
}
I have a DataGrid that has its data refreshed by a background process every 15 seconds. If any of the data changes, I want to run an animation that highlights the cell with the changed value in yellow and then fade back to white. I sort-of have it working by doing the following:
I created a style with event trigger on Binding.TargetUpdated
<Style x:Key="ChangedCellStyle" TargetType="DataGridCell">
<Style.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<BeginStoryboard>
<Storyboard>
<ColorAnimation Duration="00:00:15"
Storyboard.TargetProperty=
"(DataGridCell.Background).(SolidColorBrush.Color)"
From="Yellow" To="Transparent" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
And then applied it to the columns I wanted to highlight if a value changes
<DataGridTextColumn Header="Status"
Binding="{Binding Path=Status, NotifyOnTargetUpdated=True}"
CellStyle="{StaticResource ChangedCellStyle}" />
If the value for the status field in the database changes, the cell highlights in yellow just like I want. But, there are a few problems.
First, when the data grid is initially loaded, the entire column is highlighted in yellow. This makes sense, because all of the values are being loaded for the first time so you would expect TargetUpdated to fire. I'm sure there is some way I can stop this, but it's a relatively minor point.
The real problem is the entire column is highlighted in yellow if the grid is sorted or filtered in any way. I guess I don't understand why a sort would cause TargetUpdated to fire since the data didn't change, just the way it is displayed.
So my question is (1) how can I stop this behavior on initial load and sort/filter, and (2) am I on the right track and is this even a good way to do this? I should mention this is MVVM.
Since TargetUpdated is truly only UI update based event. It doesn't matter how update is happening. While sorting all the DataGridCells remain at their places only data is changed in them according to sorting result hence TargetUpdatedis raised. hence we have to be dependent on data layer of WPF app. To achieve this I've reset the Binding of DataGridCell based on a variable that kind of trace if update is happening at data layer.
XAML:
<Window.Resources>
<Style x:Key="ChangedCellStyle" TargetType="DataGridCell">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="DataGridCell">
<ControlTemplate.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<BeginStoryboard>
<Storyboard>
<ColorAnimation Duration="00:00:04" Storyboard.TargetName="myTxt"
Storyboard.TargetProperty="(DataGridCell.Background).(SolidColorBrush.Color)"
From="Red" To="Transparent" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</ControlTemplate.Triggers>
<TextBox HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Transparent"
Name="myTxt" >
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},Path=DataContext.SourceUpdating}" Value="True">
<Setter Property="Text" Value="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},Path=Content.Text,NotifyOnSourceUpdated=True,NotifyOnTargetUpdated=True}" />
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},Path=DataContext.SourceUpdating}" Value="False">
<Setter Property="Text" Value="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},Path=Content.Text}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<StackPanel Orientation="Vertical">
<DataGrid ItemsSource="{Binding list}" CellStyle="{StaticResource ChangedCellStyle}" AutoGenerateColumns="False"
Name="myGrid" >
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" />
<DataGridTextColumn Header="ID" Binding="{Binding Id}" />
</DataGrid.Columns>
</DataGrid>
<Button Content="Change Values" Click="Button_Click" />
</StackPanel>
Code Behind(DataContext object of Window):
public MainWindow()
{
list = new ObservableCollection<MyClass>();
list.Add(new MyClass() { Id = 1, Name = "aa" });
list.Add(new MyClass() { Id = 2, Name = "bb" });
list.Add(new MyClass() { Id = 3, Name = "cc" });
list.Add(new MyClass() { Id = 4, Name = "dd" });
list.Add(new MyClass() { Id = 5, Name = "ee" });
list.Add(new MyClass() { Id = 6, Name = "ff" });
InitializeComponent();
}
private ObservableCollection<MyClass> _list;
public ObservableCollection<MyClass> list
{
get{ return _list; }
set{
_list = value;
updateProperty("list");
}
}
Random r = new Random(0);
private void Button_Click(object sender, RoutedEventArgs e)
{
int id = (int)r.Next(6);
list[id].Id += 1;
int name = (int)r.Next(6);
list[name].Name = "update " + r.Next(20000);
}
Model Class: SourceUpdating property is set to true(which set the binding to notify TargetUpdate via a DataTrigger) when any notification is in progress for MyClass in updateProperty() method and after update is notified to UI, SourceUpdating is set to false(which then reset the binding to not notify TargetUpdate via a DataTrigger).
public class MyClass : INotifyPropertyChanged
{
private string name;
public string Name
{
get { return name; }
set {
name = value;updateProperty("Name");
}
}
private int id;
public int Id
{
get { return id; }
set
{
id = value;updateProperty("Id");
}
}
//the vaiable must set to ture when update in this calss is ion progress
private bool sourceUpdating;
public bool SourceUpdating
{
get { return sourceUpdating; }
set
{
sourceUpdating = value;updateProperty("SourceUpdating");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void updateProperty(string name)
{
if (name == "SourceUpdating")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
else
{
SourceUpdating = true;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
SourceUpdating = false;
}
}
}
Outputs:
Two simultaneous Updates/ Button is clicked once :
Many simultaneous Updates/ Button is clicked many times :
SO after update, when sorting or filtering is happening the bindings know that it doesn't have to invoke the TargetUpdated
event. Only when the update of source collection is in progress the
binding is reset to invoke the TargetUpdated event. Also initial coloring problem is also get handled by this.
However as the logic still has some sort comings as for editor TextBox the logic is based on with more complexity of data types and UI logic the code will become more complex also for initial binding reset whole row is animated as TargetUpdated is raised for all cells of a row.
My ideas for point (1) would be to handle this in the code. One way would be to handle the TargetUpdated event for the DataGridTextColumn and do an extra check on the old value vs. the new value, and apply the style only if the values are different, and perhaps another way would be to create and remove the binding programmatically based on different events in your code (like initial load, refresh, etc).
I suggest to use OnPropertyChanged for every props in your viewmodel and update related UIElement (start animation or whatever), so your problem will solved (on load, sort, filter,...) and also users can saw which cell changed!
I am refactoring three related but different DataGrids from xaml into code and hitting an issue updating the header text of a context menu.
The command and text need to update according to which data grid cell is the current cell. The header text updated fine in xaml, but as you can see from the picture below, it now shows up as an empty string. The command itself does work properly, and works on the correct grid cell.
The setter for the header text fires property changed, but I suspect my code is not replicating the binding the way the xaml equivalent does. I'm also not sure if the Shared attribute is something I need account for in code.
Does anyone see how I can improve the code I am using?
Cheers,
Berryl
XAML style to establish bindings
<ContextMenu x:Key="NonProjectActivityContextMenu" x:Shared="true">
<MenuItem
DataContext="{Binding MakeEachWeekDayFullDayCommand}" Command="{Binding .}"
Header="{Binding HeaderText}" InputGestureText="{Binding InputGestureText}"
/>
<MenuItem
DataContext="{Binding MakeFullDayCommand}" Command="{Binding .}"
Header="{Binding HeaderText}" InputGestureText="{Binding InputGestureText}"
/>
</ContextMenu>
<!-- Bindings assumes a VmMenuItem (Command Reference) -->
<Style x:Key="ContextMenuItemStyle" TargetType="{x:Type MenuItem}">
<Setter Property="Header" Value="{Binding HeaderText}"/>
<Setter Property="InputGestureText" Value="{Binding InputGestureText}" />
<Setter Property="Command" Value="{Binding Command}" />
<Setter Property="Icon" Value="{Binding Icon}" />
<Setter Property="Tag" Value="{Binding IdTag}" />
<Setter Property="ItemsSource" Value="{Binding Children}"/>
</Style>
CODE
protected virtual ContextMenu _GetContextMenu() {
var menuItems = _dataContext.MenuItems.Select(menuItem => menuItem.ToMenuItem());
var cm = new ContextMenu();
foreach (var item in menuItems) {
cm.Items.Add(item);
}
return cm;
}
UPDATE
Well the empty string part was just my own stupidity - I hadn't initialized the header text! The picture below is what I get now, which is an improvement. The text should update to say the day of the week tho, ie, "Make Monday a full day"
EDIT for Erno
I am setting columns and the style for the grid itself as below, so I thought I can just fetch the resource for the context menu and set it.
Am getting an odd result however, as you can see from the pic - it's like the context menu is covering the whole grid!
private void OnDataGridLoaded(object sender, RoutedEventArgs e)
{
_dataContext = (ActivityCollectionViewModel)DataContext;
IsSynchronizedWithCurrentItem = true;
Style = (Style)FindResource(GRID_STYLE_NAME);
_AddColumns();
var timeSheetColumns = Columns.Cast<TimesheetGridColumn>();
foreach (var col in timeSheetColumns)
{
col.SetHeader();
col.SetCellStyle(this);
col.SetBinding();
}
if(DesignerProperties.GetIsInDesignMode(this)) {
// just so the designer doesn't hit a null reference on the data context
ItemsSource = new ObservableCollection<ActivityViewModel>();
}
else {
// ok, we have a runtime data context to work with
ItemsSource = _dataContext.ActivityVms;
InputBindings.AddRange(_GetKeyBindings());
ContextMenu = _GetContextMenu();
ContextMenu.Style = (Style)FindResource("ContextMenuItemStyle");
}
}
private void OnDataGridLoaded(object sender, RoutedEventArgs e)
{
_dataContext = (ActivityCollectionViewModel)DataContext;
IsSynchronizedWithCurrentItem = true;
Style = (Style)FindResource(GRID_STYLE_NAME);
_AddColumns();
var timeSheetColumns = Columns.Cast<TimesheetGridColumn>();
foreach (var col in timeSheetColumns)
{
col.SetHeader();
col.SetCellStyle(this);
col.SetBinding();
}
if(DesignerProperties.GetIsInDesignMode(this)) {
// just so the designer doesn't hit a null reference on the data context
ItemsSource = new ObservableCollection<ActivityViewModel>();
}
else {
// ok, we have a runtime data context to work with
ItemsSource = _dataContext.ActivityVms;
InputBindings.AddRange(_GetKeyBindings());
ContextMenu = _GetContextMenu();
ContextMenu.Style = (Style)FindResource("ContextMenuItemStyle");
}
}
Latest Update
I tried making my binding relative per this SO post but no dice. My command updated, meaning it executed on the correct cell, but I couldn't get the text to reflect which cell it was. I finally just decided to build the context menu on the fly as below. It work fine, although it seems I should have been able to do better.
Am going to give the answer to Erno and close this out.
private void OnCurrentCellChanged(object sender, EventArgs e)
{
if (ReferenceEquals(null, sender)) return;
var grid = (DataGrid)sender;
var selectedActivity = (ActivityViewModel)grid.CurrentItem;
if (ReferenceEquals(selectedActivity, null)) return;
if (_isEditableDayOfTheWeekColumn(grid.CurrentColumn))
{
var dowCol = (DayOfTheWeekColumn)grid.CurrentColumn;
var index = Convert.ToInt32(dowCol.DowIndex);
selectedActivity.SetSelectedAllocationVm(index);
}
else
{
selectedActivity.SetSelectedAllocationVm(-1);
}
var commands = selectedActivity
.AllCommands
.Select(vmMenuItem => vmMenuItem.Command.ToMenuItem());
var cm = new ContextMenu();
foreach (var item in commands)
{
//item.SetResourceReference(StyleProperty, "ContextMenuItemStyle");
cm.Items.Add(item);
}
grid.ContextMenu = cm;
}
My guess is you want to use a Style in code as well.
Just create an instance of the Style class, set its properties (including the binding) and add it to a Resources property in the tree.
Next, use the style in the generated menu items.
I'm having trouble getting the autocomplete box in System.Windows.Controls.Input working as I wish. When I start typing the dropdown section that displays the filtered list doesn't show the property that I'm binding to, it shows the class name instead.
So in the example below, when I type in my - instead of showing 'My Name' it shows MyNamespace.Person. However, when I select the item from the autocomplete list, it displays the FullName property in the textbox. I'm sure I'm just missing a simple autocomplete box property somewhere but I can't see it.
Example code:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string FullName
{
get { return string.Format("{0} {1}", FirstName, LastName); }
}
}
In my xaml code behind I create some Person objects and store them in a list and bind that list to an autocomplete box
List<Person> people = new List<Person>();
people.Add(new Person { FirstName = "My", LastName = "Name" });
people.Add(new Person { FirstName = "Fernando", LastName = "Torres" });
acbNames.ItemsSource = people;
My xaml:
<my:AutoCompleteBox Name="acbNames" ValueMemberPath="FullName" />
/* after entering 'my', auto complete displays 'MyNamespace.Person' instead of 'My Name', but displays 'My Name' after selecting the item from the list */
It turns out I need to use an ItemTemplate for the dropdown part of the AutoCompleteBox, so the xaml for it would now be as follows:
<my:AutoCompleteBox Name="acbNames" ValueMemberBinding="{Binding FullName}">
<my:AutoCompleteBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding FullName}"/>
</DataTemplate>
</my:AutoCompleteBox.ItemTemplate>
</my:AutoCompleteBox>
Yes, your problem was because you didn't put item template.
But if you put item template and still got problem read what Sandro has wroted.
I had same problem. I solved it using a Static resource for the Control Style
This is the style i used:
<Style x:Key="autocomplete" TargetType="sdk1:AutoCompleteBox">
<Setter Property="Margin" Value="5,0,5,0"/>
<Setter Property="MinWidth" Value="100"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property ="HorizontalAlignment" Value="Right"/>
</Style>
If I don't use this style my Customs Item are not displayed correctly as I configure in DataItem, instead it show the Class name.
share|edit
This works for me too but only when i applied some custom theme style from toolkit.
There are some other workarounds when you use theme from toolkit
Best,
debarisi
I had same problem. I solved it using a Static resource for the Control Style
This is the style i used:
<Style x:Key="autocomplete" TargetType="sdk1:AutoCompleteBox">
<Setter Property="Margin" Value="5,0,5,0"/>
<Setter Property="MinWidth" Value="100"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property ="HorizontalAlignment" Value="Right"/>
</Style>
If I don't use this style my Customs Item are not displayed correctly as I configure in DataItem, instead it show the Class name.