I have this XAML in a WPF project.
<ListView Grid.Column="0" Grid.Row="3" Grid.RowSpan="4" Grid.ColumnSpan="2" ItemsSource="{Binding FilteredApps}" SelectedItem="{Binding SelectedApp, Mode=TwoWay}">
<ListView.View>
<GridView>
<GridViewColumn Header="Status">
<GridViewColumn.CellTemplate >
<DataTemplate DataType="{x:Type Image}">
<Image>
<Image.Style>
<Style TargetType="Image">
<Style.Triggers>
<DataTrigger Binding="{Binding State, Mode=TwoWay}" Value="{x:Static common:Globals+ModelState.Unedited}">
<Setter Property="Source" Value="../Resources/IndraDoc/AppUnedited.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding State, Mode=TwoWay}" Value="{x:Static common:Globals+ModelState.Edited}">
<Setter Property="Source" Value="../Resources/IndraDoc/AppEdited.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding State}" Value="{x:Static common:Globals+ModelState.New}">
<Setter Property="Source" Value="../Resources/IndraDoc/AppNew.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding State}" Value="{x:Static common:Globals+ModelState.Deleted}">
<Setter Property="Source" Value="../Resources/IndraDoc/AppDeleted.png"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn DisplayMemberBinding="{Binding Name}" Header="Namn" Width="Auto"/>
</GridView>
</ListView.View>
</ListView>
When property "Name" changes the appropriate change is displayed in the Grid, but when the property "State" changes the Image is not switched. Any takes on this?
EDIT
Strange thing is this, I edited the XAML and added the image outside of the ListView bound it to the SelectedApp instead, this works fine, however that was not the intended functionality. It will work for now, but I am still curious as to why my original code won't trigger.
Adding the code where I do the statechange:
private void ChangeStateOnSelected(Globals.ModelState newState)
{
if (SelectedApp.State != newState) //dont do any changework if new state is same as current sate
{
if (SelectedApp.State == Globals.ModelState.New && newState == Globals.ModelState.Edited) //this is not allowed
return;
SelectedApp.State = newState;
if (newState == Globals.ModelState.Unedited)
{
var selectedId = SelectedApp.Id;
_allApps.Replace(_allApps.FirstOrDefault(x => x.Id == SelectedApp.Id), SelectedApp);
_selectedApp = (Data.DataModel.App)_locationService.GetApp(selectedId);
_filteredApps.Replace(_filteredApps.FirstOrDefault(x => x.Id == SelectedApp.Id), SelectedApp);
}
NotifyOfPropertyChange(() => SelectedApp);
NotifyOfPropertyChange(() => AllApps);
NotifyOfPropertyChange(() => FilteredApps);
}
}
Related
I have a WPF Datagrid that is filled with a CollectionViewSource and what I want is to change the row background color depending on a binded property value.
Red if value is "KO"
Green if value is "OK"
Yellow if value is "Created"
I've read different threads here on SO such as Change Data bound WPF Datagrid row background/foreground color depending on the boolean property of an object or WPF Datagrid row colour based on value but still struggling to make this work.
I don't know what is wrong with my code. Here is what I tried:
Main.Xaml
<UserControl.DataContext>
<Viewmodel:LoadServerViewModel/>
</UserControl.DataContext>
<Grid Margin="10,20,20,20" Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="6">
<DataGrid AutoGenerateColumns="False"
ScrollViewer.CanContentScroll="True"
ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.HorizontalScrollBarVisibility="Visible"
VerticalAlignment="Stretch"
IsReadOnly="False"
ItemsSource="{Binding Servers}">
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding RowStatus}" Value="Created">
<Setter Property="Background" Value="Yellow" />
</DataTrigger>
<DataTrigger Binding="{Binding RowStatus}" Value="OK">
<Setter Property="Background" Value="Green" />
</DataTrigger>
<DataTrigger Binding="{Binding RowStatus}" Value="KO">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" />
<DataGridTextColumn Header="Last Update" Binding="{Binding LastUp} />
....
</DataGrid.Columns>
</DataGrid>
</Grid>
<Button Style="{DynamicResource RoundedButtonStyle}" Grid.Row="1" Grid.Column="3"
Content="Refresh"
Margin="0,0,40,0"
Height="25"
Width="90"
Foreground="#FFFFFF"
Background="#2f3640"
Command="{Binding ButtonRefresh}">
</Button>
LoadServerViewModel.cs
private string _rowStatus = "Created";
private readonly RelayCommand _buttonRefresh;
public string RowStatus
{
get => _rowStatus;
set
{
_rowStatus = value;
OnPropertyChanged(nameof(RowStatus));
}
}
public ICollectionView Servers
{
get
{
if (_collectionViewSource.View != null)
{
_collectionViewSource.View.CurrentChanged += (sender, e) => ServerGridFormat = _collectionViewSource.View.CurrentItem as ServerGridFormat;
return _collectionViewSource?.View;
}
return null;
}
}
public ServerGridFormat ServerGridFormat
{
get => _servergridformat;
set
{
servergridformat = value;
OnPropertyChanged(nameof(ServerGridFormat));
}
}
public LoadServerViewModel() {
// Button to trigger the change of RowStatus from "Created" to "OK" therefore the color of all rows
_buttonRefresh = new RelayCommand(o => RowStatus = "OK");
}
Thanks for your help
You current binding uses the DataContext of the individual row as binding source (which is the data model stored in the ItemsSource). But your property RowStatus is defined on the object that is the DataContext of the DataGrid (which is the LoadServerViewModel).
To make it work, you must adjust your data bindings to use the correct source (or alternatively move the RowSatus property to the data models):
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=DataGrid}, Path=DataContext.RowStatus}"
Value="Created">
<Setter Property="Background" Value="Yellow" />
</DataTrigger>
</Style.Triggers>
</Style>
I have this style for ContentControl:
<UserControl.Resources>
<DataTemplate x:Key="textbox">
<TextBox Text="edit me"/>
</DataTemplate>
<DataTemplate x:Key="textblock">
<TextBlock Text="can't edit"/>
</DataTemplate>
<Style x:Key="ContentControlStyle" TargetType="{x:Type ContentControl}">
<Setter Property="Content" Value="{Binding}"/>
<Setter Property="ContentTemplate" Value="{StaticResource textblock}" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected,RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ListViewItem},AncestorLevel=1}}"
Value="True">
<Setter Property="ContentTemplate" Value="{StaticResource textbox}" />
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
And that codes:
<ListView.View>
<GridView x:Name="UGridview1">
<GridViewColumn Width=" 90">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ContentControl >
</ContentControl>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
But I want to create the columns dynamically, so I wrote the following codes:
for (x = 0; x <= Lvobj.obj.Length - 1; x++) // ClmnCount - 1
{
GridViewColumn_ = new GridViewColumn();
GridViewColumn_.SetValue(NameProperty, "Column" + x);
GridViewColumn_.Header = Lvobj.obj(x)(clmntxt);
GridViewColumn_.Width = 99;
/// This part doesnt work
ContentControl cntr = new ContentControl();
cntr.Style = this.Resources("ContentControlStyle");
///
GridViewColumn_.CellTemplate = cntr.ContentTemplate;
UGridview1.Columns.Add(GridViewColumn_);
}
It never works. What must i do for i can create columns with ContentControl Style?
Either use XamlReader.Parse API with a DynamicResource:
const string Xaml = #"<DataTemplate " +
#"xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""> " +
#"<ContentControl Style=""{DynamicResource ContentControlStyle}"" />" +
"</DataTemplate>";
DataTemplate DataTemplate_ = System.Windows.Markup.XamlReader.Parse(Xaml) as DataTemplate;
GridViewColumn_.CellTemplate = DataTemplate_;
Or create a FrameworkElementFactory:
FrameworkElementFactory cc = new FrameworkElementFactory(typeof(ContentControl));
cc.SetValue(ContentControl.StyleProperty, this.Resources["ContentControlStyle"]);
GridViewColumn_.CellTemplate = new DataTemplate() { VisualTree = cc };
Access the ResourceDictionary with square brackets (it's a Dictionary):
this.Resources["ContentControlStyle"]
Make sure to not lookup the style before UserControl.OnInitialized is called. You can override OnInitialized in your UserControl and then initialize the ListView. Alternatively handle the Loaded event.
Alternatively (recommended), consider to define the style implicit (without a x:key). Then the style will be applied automatically, once the target is loaded. This way you don't have to deal with the resources.
You can limit the scope by defining it inside the ResourceDictionary of the ListView:
<ListView>
<ListView.Resources>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="Content"
Value="{Binding}" />
<Setter Property="ContentTemplate"
Value="{StaticResource textblock}" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected,RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ListViewItem},AncestorLevel=1}}"
Value="True">
<Setter Property="ContentTemplate"
Value="{StaticResource textbox}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.Resources>
<ListView.View>
<GridView x:Name="UGridview1">
<GridViewColumn Width=" 90">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ContentControl />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
Code:
private void updateSeconderyCombobox(object sender, SelectionChangedEventArgs e)
{
object comboBox = null;
if (sender is ComboBox)
{
comboBox = (ComboBox)sender;
}
else
{
comboBox = (TextBlock)sender;
}
DataGridRow row = (DataGridRow)dataGrid.ContainerFromElement((DependencyObject)comboBox);
xyEntry newValues = null;
if (row.Item != null)
{
newValues = (xyEntry)row.Item;
}
else
{
return;
}
Factors fs = new Factors();
List<Factor> list = fs.getFactors();
Factor f = list.Find(x => x.Category == newValues.Category);
newValues.FactorX = Convert.ToString(f.FactorXvalue);
newValues.FactorY = Convert.ToString(f.FactorYvalue);
newValues.SizeChoices = AddSeconderyCombobox(newValues.Category);
row.Item = null;
row.Item = newValues;
}
XAML:
<DataGrid SelectionUnit="FullRow" x:Name="dataGrid" Margin="10,110,162,15" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" AutoGenerateColumns="False" CanUserAddRows="False" CanUserReorderColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="X Coord" Binding="{Binding xCoord}" IsReadOnly="True" Width="*" />
<DataGridTextColumn Header="Y Coord" Binding="{Binding yCoord}" IsReadOnly="True" Width="*"/>
<DataGridTextColumn Header="Rotation" Binding="{Binding Rotation}" IsReadOnly="True" Width="*"/>
<DataGridTextColumn Header="RefDes" Binding="{Binding RefDes}" IsReadOnly="True" Width="*"/>
<DataGridTextColumn Header="P/N" Binding="{Binding Number}" IsReadOnly="True" Width="*"/>
<DataGridTextColumn Header="Package" Binding="{Binding Package}" IsReadOnly="True" Width="*"/>
<DataGridTextColumn Header="FactorX %" Binding="{Binding FactorX}" IsReadOnly="False" Width="*"/>
<DataGridTextColumn Header="FactorY %" Binding="{Binding FactorY}" IsReadOnly="False" Width="*"/>
<DataGridCheckBoxColumn Header="Use factors" Binding="{Binding UseFactors}" IsReadOnly="False" Width="*"/>
<DataGridTemplateColumn Header="Category" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<ComboBox SelectedValue="{Binding Category, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" ItemsSource="{Binding CategoryChoices}" SelectionChanged="updateSeconderyCombobox" >
<ComboBox.Style>
<Style TargetType="ComboBox">
<Setter Property="Visibility" Value="Hidden"/>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGridRow}, Path=IsSelected}"
Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
<TextBlock Text="{Binding Category}" >
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGridRow}, Path=IsSelected}"
Value="True">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Size" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<ComboBox SelectedValue="{Binding Size, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" ItemsSource="{Binding SizeChoices}">
<ComboBox.Style>
<Style TargetType="ComboBox">
<Setter Property="Visibility" Value="Hidden"/>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGridRow}, Path=IsSelected}"
Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
<TextBlock Text="{Binding Size}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGridRow}, Path=IsSelected}"
Value="True">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
System.NullReferenceException
HResult=0x80004003 Message=Object reference not set to an instance of an object.
Source=IsoEasy
StackTrace: at
IsoEasy.Windows.Link_parts.updateSeconderyCombobox(Object sender,
SelectionChangedEventArgs e) at
System.Windows.Controls.SelectionChangedEventArgs.InvokeEventHandler(Delegate
genericHandler, Object genericTarget) at
System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object
target) at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object
target, RoutedEventArgs routedEventArgs) at
System.Windows.EventRoute.InvokeHandlersImpl(Object source,
RoutedEventArgs args, Boolean reRaised) at
System.Windows.UIElement.RaiseEventImpl(DependencyObject sender,
RoutedEventArgs args) at
System.Windows.UIElement.RaiseEvent(RoutedEventArgs e) at
System.Windows.Controls.ComboBox.OnSelectionChanged(SelectionChangedEventArgs
e)
......
What occurs:
Each row of the Datagrid contains 2 comboboxes. The first combobox is a static list of 6 categories, the second combobox get's populated depending on what category is selected. This works fine on 99% of the PC's I've tested. However, I have one PC where this gives a problem. The error occurs on:
DataGridRow row = (DataGridRow)dataGrid.ContainerFromElement((DependencyObject)comboBox);
xyEntry newValues = null;
if (row.Item != null){
newValues = (xyEntry)row.Item;
}
else {
return;
}
In the situation where this works, I see that the sender parameter contains the static list for the first combobox. But in this one case where this doesn't work the sender seems to be an empty combobox. This results in the fact that "row" variable gets set as null and I get a System.NullReferenceException when I check the items of this variable in the if statement.
What I tried:
First of all, I made sure the correct .Net version was installed. This had no effect.
I tried a hotfix by trying to set the itemsource of the sender after casting it to the correct data type, but this also throws a NullReferenceException.
The same windows versions are running on both machines
I think I had a similar issue with NullReferenceException and adding DataGridRow row = new DataGridRow(); before assigning value to row solved this problem.
Check whether the ComboBox has been loaded before you try to do anything. You should also avoid casting to anything else than a FrameworkElement:
private void updateSeconderyCombobox(object sender, SelectionChangedEventArgs e)
{
FrameworkElement comboBox = sender as FrameworkElement;
if (comboBox == null || !comboBox.IsLoaded)
return;
DataGridRow row = (DataGridRow)dataGrid.ContainerFromElement(comboBox);
...
}
I have a related question with a proposed answer here but my issue is more complex and I think it is best to start a new question. I hope that is OK.
I think it is important to visually show you what I am trying to do first. Here is an overview of the editor:
Down the left are several controls that are used to display the chosen students (and assistants where applicable). The editor supports 3 classes for the students. Thus, I have the students for each class on a TabItem control.
Example:
As you can see, it has a TabControl with 3 items (Main Hall, Class 1 and Class 2). On each of those tab items they then have their own TabControl with the 3 student items.
You see those small icons next to the text boxes? If you click those then they affect a combo that is over on the right side of the editor:
So when you click a small icon, it automatically selects the right entry in the combo on the right. Then, as you select a student from the grid it gets populated into the right part of the editor:
The Issue
When the user manually changes the combo over on the right, it then needs to set a focus rectangle over on th left hand side correctly. So when a item is selected it needs to:
Set the tab item as selected (if needed).
Set the border brush of the rectangle to red instead of transparent.
I have got it to set the rectangle colours as required but I have not yet managed to do that using behaviours (as per the related question). In addition, when I try fiddling with the IsSelected property of the 'TabItem' objects I find that they sometimes kind of collapse on screen. I have commented out my code.
I am sorry for the wordier question but I will get asked for clarification so I am trying to cover it up front.
So:
There are two main TabControl objects:
The first is the bible reading items:
<TabControl Grid.Row="1" Grid.ColumnSpan="2">
<TabItem x:Name="tabTFGWReadingMainHall" Header="Main Hall" DataContext="{Binding Meeting}">
<TabItem.Style>
<Style TargetType="{x:Type TabItem}">
<Setter Property="IsEnabled" Value="False"/>
<!--<Setter Property="IsSelected" Value="False"/>-->
<Style.Triggers>
<DataTrigger Binding="{Binding NumberClasses, ConverterParameter=1, Converter={StaticResource IsEqualOrGreaterThanConverter}}" Value="True">
<Setter Property="IsEnabled" Value="True" />
</DataTrigger>
<!--<DataTrigger Binding="{Binding SelectedItem, ElementName=comboActiveStudentAssignmentType}" Value="{x:Static StudentInfoEnums:StudentAssignmentType.BibleReadingMain}">
<Setter Property="IsSelected" Value="True"/>
</DataTrigger>-->
</Style.Triggers>
</Style>
</TabItem.Style>
<Border x:Name="borderBibleReadingMain" BorderThickness="5">
<Border.Style>
<Style TargetType="Border">
<Setter Property="BorderBrush" Value="Transparent"/>
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedItem, ElementName=comboActiveStudentAssignmentType}" Value="{x:Static StudentInfoEnums:StudentAssignmentType.BibleReadingMain}">
<Setter Property="BorderBrush" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<StackPanel>
<StackPanel.Style>
<Style TargetType="{x:Type StackPanel}">
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<DataTrigger Binding="{Binding NumberClasses, ConverterParameter=1, Converter={StaticResource IsEqualOrGreaterThanConverter}}" Value="True">
<Setter Property="IsEnabled" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<Label Content="Student:"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="textBibleReadingMain" Grid.Column="0" Margin="2" IsEnabled="False"
Text="{Binding BibleReadingMainName, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
<Button x:Name="buttonBibleReadingMain" Grid.Column="1" Background="Transparent"
DataContext="{Binding DataContext, ElementName=oclmEditor}"
Command="{Binding ApplicationCommand}" CommandParameter="BibleReadingMain">
<Image Source="Images/AssignmentTypeBibleReading16.png" Margin="2"/>
</Button>
</Grid>
<Label Content="Study:"/>
<ComboBox DataContext="{Binding DataContext, ElementName=oclmEditor}"
ItemsSource="{Binding ReadingStudyPointsList}"
ItemContainerStyle="{StaticResource StudyPointComboBoxStyle}"
ItemTemplate="{StaticResource StudyPointComboItem}"
Validation.ErrorTemplate="{StaticResource StudyPointValidationTemplate}"
Tag="{Binding Meeting.BibleReadingMainName, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
SelectedValue="{Binding Meeting.BibleReadingMainStudyPoint, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="Number">
<!--<ComboBox.Style>
<Style
TargetType="ComboBox"
BasedOn="{StaticResource {x:Type ComboBox}}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter
Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
<ComboBox.SelectedValue>
<Binding Path="Meeting.BibleReadingMainStudyPoint"
Mode="TwoWay"
UpdateSourceTrigger="PropertyChanged"
NotifyOnValidationError="True">
<Binding.ValidationRules>
<ValidationRules:StudyPointValidationRule BibleReading="True"/>
</Binding.ValidationRules>
</Binding>
</ComboBox.SelectedValue>-->
</ComboBox>
</StackPanel>
</Border>
</TabItem>
<TabItem x:Name="tabTFGWReadingClass1" Header="Class 1" DataContext="{Binding Meeting}">
<TabItem.Style>
<Style TargetType="{x:Type TabItem}">
<Setter Property="IsEnabled" Value="False"/>
<Setter Property="IsSelected" Value="False"/>
<Style.Triggers>
<DataTrigger Binding="{Binding NumberClasses, ConverterParameter=2, Converter={StaticResource IsEqualOrGreaterThanConverter}}" Value="True">
<Setter Property="IsEnabled" Value="True" />
</DataTrigger>
<DataTrigger Binding="{Binding SelectedItem, ElementName=comboActiveStudentAssignmentType}" Value="{x:Static StudentInfoEnums:StudentAssignmentType.BibleReadingClass1}">
<Setter Property="IsSelected" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TabItem.Style>
<Border x:Name="borderBibleReadingClass1" BorderThickness="5">
<Border.Style>
<Style TargetType="Border">
<Setter Property="BorderBrush" Value="Transparent"/>
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedItem, ElementName=comboActiveStudentAssignmentType}" Value="{x:Static StudentInfoEnums:StudentAssignmentType.BibleReadingClass1}">
<Setter Property="BorderBrush" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<StackPanel>
<StackPanel.Style>
<Style TargetType="{x:Type StackPanel}">
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<DataTrigger Binding="{Binding NumberClasses, ConverterParameter=2, Converter={StaticResource IsEqualOrGreaterThanConverter}}" Value="True">
<Setter Property="IsEnabled" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<Label Content="Student:"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="textBibleReadingClass1" Grid.Column="0" Margin="2" IsEnabled="False"
Text="{Binding BibleReadingClass1Name, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
<Button x:Name="buttonBibleReadingClass1" Grid.Column="1" Background="Transparent"
DataContext="{Binding DataContext, ElementName=oclmEditor}"
Command="{Binding ApplicationCommand}" CommandParameter="BibleReadingClass1">
<Image Source="Images/AssignmentTypeBibleReading16.png" Margin="2"/>
</Button>
</Grid>
<Label Content="Study:"/>
<ComboBox DataContext="{Binding DataContext, ElementName=oclmEditor}"
ItemsSource="{Binding ReadingStudyPointsList}"
ItemContainerStyle="{StaticResource StudyPointComboBoxStyle}"
ItemTemplate="{StaticResource StudyPointComboItem}"
Tag="{Binding Meeting.BibleReadingClass1Name, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
SelectedValue="{Binding Meeting.BibleReadingClass1StudyPoint, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="Number"/>
</StackPanel>
</Border>
</TabItem>
<TabItem x:Name="tabTFGWReadingClass2" Header="Class 2" DataContext="{Binding Meeting}">
<TabItem.Style>
<Style TargetType="{x:Type TabItem}">
<Setter Property="IsEnabled" Value="False"/>
<Setter Property="IsSelected" Value="False"/>
<Style.Triggers>
<DataTrigger Binding="{Binding NumberClasses, ConverterParameter=3, Converter={StaticResource IsEqualOrGreaterThanConverter}}" Value="True">
<Setter Property="IsEnabled" Value="True" />
</DataTrigger>
<DataTrigger Binding="{Binding SelectedItem, ElementName=comboActiveStudentAssignmentType}" Value="{x:Static StudentInfoEnums:StudentAssignmentType.BibleReadingClass2}">
<Setter Property="IsSelected" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TabItem.Style>
<Border x:Name="borderBibleReadingClass2" BorderThickness="5">
<Border.Style>
<Style TargetType="Border">
<Setter Property="BorderBrush" Value="Transparent"/>
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedItem, ElementName=comboActiveStudentAssignmentType}" Value="{x:Static StudentInfoEnums:StudentAssignmentType.BibleReadingClass2}">
<Setter Property="BorderBrush" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<StackPanel>
<StackPanel.Style>
<Style TargetType="{x:Type StackPanel}">
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<DataTrigger Binding="{Binding NumberClasses, ConverterParameter=3, Converter={StaticResource IsEqualOrGreaterThanConverter}}" Value="True">
<Setter Property="IsEnabled" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<Label Content="Student:"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="textBibleReadingClass2" Grid.Column="0" Margin="2" IsEnabled="False"
Text="{Binding BibleReadingClass2Name, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
<Button x:Name="buttonBibleReadingClass2" Grid.Column="1" Background="Transparent"
DataContext="{Binding DataContext, ElementName=oclmEditor}"
Command="{Binding ApplicationCommand}" CommandParameter="BibleReadingClass2">
<Image Source="Images/AssignmentTypeBibleReading16.png" Margin="2"/>
</Button>
</Grid>
<Label Content="Study:"/>
<ComboBox DataContext="{Binding DataContext, ElementName=oclmEditor}"
ItemsSource="{Binding ReadingStudyPointsList}"
ItemContainerStyle="{StaticResource StudyPointComboBoxStyle}"
ItemTemplate="{StaticResource StudyPointComboItem}"
Tag="{Binding Meeting.BibleReadingClass2Name, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
SelectedValue="{Binding Meeting.BibleReadingClass2StudyPoint, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="Number"/>
</StackPanel>
</Border>
</TabItem>
</TabControl>
The second is the tabControlAYFMStudents control but I have to keep my question body limited. So I have not shown it.
I don't expect anyone to read through all my code. So if you think I should remove this question then please advise me so. Or if you think I can ask it somewhere else.
I would just like to be able to get the rectangle border set as transparent (or red) based on the current combo item selected. And, ensure it is visible.
Phew! Thank you.
I decided during the night that using the code behind would be the simplest solution for my issues. I added a handler for the SelectionChanged event:
private void comboActiveStudentAssignmentType_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
List<Border> borders = new List<Border>();
// The list of border (focus rectangles) matches the combo of assignment types
borders.Add(borderBibleReadingMain);
borders.Add(borderBibleReadingClass1);
borders.Add(borderBibleReadingClass2);
borders.Add(borderMainHallStudent1);
borders.Add(borderMainHallAssistant1);
borders.Add(borderMainHallStudent2);
borders.Add(borderMainHallAssistant2);
borders.Add(borderMainHallStudent3);
borders.Add(borderMainHallAssistant3);
borders.Add(borderClass1Student1);
borders.Add(borderClass1Assistant1);
borders.Add(borderClass1Student2);
borders.Add(borderClass1Assistant2);
borders.Add(borderClass1Student3);
borders.Add(borderClass1Assistant3);
borders.Add(borderClass2Student1);
borders.Add(borderClass2Assistant1);
borders.Add(borderClass2Student2);
borders.Add(borderClass2Assistant2);
borders.Add(borderClass2Student3);
borders.Add(borderClass2Assistant3);
// Loop through the borders
for(int iBorder = 0; iBorder < borders.Count; iBorder++)
{
// Is this border the active student assignment?
if (comboActiveStudentAssignmentType.SelectedIndex == iBorder)
{
// Yes, so use a red brush for the background
borders[iBorder].BorderBrush = Brushes.Red;
// Now we must ensure the correct tab item is visible
if(iBorder >= 0 && iBorder <= 2)
{
expandTFGW.IsExpanded = true;
if (iBorder == 0)
tabTFGWReadingMainHall.IsSelected = true;
else if (iBorder == 1)
tabTFGWReadingClass1.IsSelected = true;
else if (iBorder == 2)
tabTFGWReadingClass2.IsSelected = true;
}
else if (iBorder >= 3 && iBorder <= 8)
{
expandAYFM.IsExpanded = true;
tabAYFMStudentsMainHall.IsSelected = true;
if (iBorder == 3 || iBorder == 4)
tabMainHallItem1.IsSelected = true;
else if (iBorder == 5 || iBorder == 6)
tabMainHallItem2.IsSelected = true;
else if (iBorder == 7 || iBorder == 8)
tabMainHallItem3.IsSelected = true;
}
else if (iBorder >= 9 && iBorder <= 14)
{
expandAYFM.IsExpanded = true;
tabAYFMStudentsClass1.IsSelected = true;
if (iBorder == 9 || iBorder == 10)
tabClass1Item1.IsSelected = true;
else if (iBorder == 11 || iBorder == 12)
tabClass1Item2.IsSelected = true;
else if (iBorder == 13 || iBorder == 14)
tabClass1Item3.IsSelected = true;
}
else if (iBorder >= 15)
{
expandAYFM.IsExpanded = true;
tabAYFMStudentsClass2.IsSelected = true;
if (iBorder == 15 || iBorder == 16)
tabClass2Item1.IsSelected = true;
else if (iBorder == 17 || iBorder == 18)
tabClass2Item2.IsSelected = true;
else if (iBorder == 19 || iBorder == 20)
tabClass2Item3.IsSelected = true;
}
borders[iBorder].BringIntoView();
}
else
{
// No, so set the background to transparent so we can't see it.
borders[iBorder].BorderBrush = Brushes.Transparent;
}
}
}
}
This works fine. As you change the combo the correct Border is set to red and it's associated TabItem is set to Selected.
In addition, I set the associated Expander to expanded. This means I am 95% where I want to be. I can remove all of the DataTriggers from the XAML as it is all now managed in this one handler.
However, this call doesn't seem to work:
borders[iBorder].BringIntoView();
So even though the above will set the border properties and ensure it's tab item is selected, I can't ensure the control is visible if it is off-screen.
Update
This answer explains how tobring the rectangle into view:
var localBorderIndex = iBorder; // copy to avoid closure of loop variable
Dispatcher.InvokeAsync(() => borders[localBorderIndex].BringIntoView(),
DispatcherPriority.Background);
I have a WPF Window and a Viewmodel and I need to change the content of a button to fit the property of the SelectedItem in the Datagrid.
For example:
The DataGrid is bound to a list of tasks with two properties: Name and EndDate. If EndDate is null I want the button to display 'End Task' and if it is not null to display 'Resume Task'
I managed to hide the button if SelectedItem is null, but I can't figure out a way to do this.
Here is the XAML code that I have for the button:
<DataGrid AutoGenerateColumns="False"
ItemsSource="{Binding Tasks}"
x:Name="dg"
SelectedItem="{Binding SelectedTask}">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
<DataGridTextColumn Header="End Date" Binding="{Binding EndDate}"/>
</DataGrid.Columns>
</DataGrid>
<Button Content="{Binding ButtonText}"
Name="btn_close_resume"
Command="{Binding CloseResumeCommand}"
CommandParameter="{Binding ElementName=dg, Path=SelectedItem}">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedTask}" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
On the ViewModel I have the ButtonText property like this:
public string ButtonText
{
get { return this.SelectedTask.EndDate == null ? "Close Task" : "Resume Task"; }
}
But the Button doesn't display any text.
How can I do this?
You can use another DataTrigger to set the content on the button based on the SelectedTask EndDate value
<Button
Name="btn_close_resume"
Command="{Binding CloseResumeCommand}"
CommandParameter="{Binding ElementName=dg, Path=SelectedItem}">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Content" Value="Resume Task"/>
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedTask}" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding SelectedTask.EndDate}" Value="{x:Null}">
<Setter Property="Content" Value="Resume Task"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
How ever as you didn't say what's the default value of the button's content when there's nothing selected on the datagridview, the button will say "Resume Task" until something gets selected. If you need to change this, you will need to use a converter.
If you really want to do it through the view model, you have to notify the changes in the SelectedTask setter:
public Task SelectedTask
{
get { return selectedTask; }
set
{
selectedTask = value;
OnPropertyChanged("SelectedTask");
OnPropertyChanged("ButtonText");
}
}
public string ButtonText
{
get { return this.SelectedTask.EndDate == null ? "Close Task" : "Resume Task"; }
}