Icon/Button That changes based on boolean binding value in MVVM - wpf

I've gotten this far with my XAML and wondering if I'm taking the right approach with regards to an icon/button to display data in a datagrid. I'm using Material Design.
I've got a boolean property in the viewmodel called "IsLocked". I want an icon in a column which will change to a lock or unlocked icon based on the value and I want to be able to click on the lock to change the value. I'm missing the button/click event. I'm trying to keep it in the XAML.
A bonus would be some UI feedback on mouseover to let the user know they can click it. How can I incorporate that too?
Is this the correct approach? How do I make it into something click-able?
<DataGridTemplateColumn Width="Auto" Header="Is Locked">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<materialDesign:PackIcon x:Name="LockIcon" Kind="LockOpenOutline" Foreground="Green"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding isLocked}" Value="True">
<Setter Property="Kind" Value="Lock" TargetName="LockIcon"/>
<Setter Property="Foreground" Value="Red" TargetName="LockIcon"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>

You can create a converter which will return the PackIconKind and bind the isLocked property to the PackIcon with the converter.

Related

DataTrigger in CellTemplate binding to HeaderTemplate; can it work?

The goal here would be to check all grid checkboxes if the header checkbox changes:
<Window.Resources>
<Style TargetType="CheckBox" x:Key="InnerBox">
<Setter Property="HorizontalAlignment" Value="Center" />
<Style.Triggers>
<DataTrigger Value="True"
Binding="{Binding IsChecked,
ElementName=HeaderCheckbox}">
<Setter Property="IsChecked" Value="True" />
</DataTrigger>
<DataTrigger Value="False"
Binding="{Binding IsChecked,
ElementName=HeaderCheckbox}">
<Setter Property="IsChecked" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<DataGrid>
<DataGrid.Columns>
<!-- col1 -->
<DataGridTemplateColumn>
<DataGridTemplateColumn.HeaderTemplate>
<DataTemplate>
<!-- header check -->
<CheckBox Name="HeaderCheckbox" />
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<!-- body check -->
<CheckBox Style="{StaticResource InnerBox}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!-- col2 -->
<DataGridTextColumn Binding="{Binding}" Header="Text" />
</DataGrid.Columns>
<!-- sample data -->
<sys:String>1</sys:String>
<sys:String>2</sys:String>
<sys:String>3</sys:String>
</DataGrid>
Looks like:
For some reason, the trigger does not fire.
Any ideas?
ElementName binding inside a DataTemplate can't reach an element outside of the template as you noticed. This is because it can be instantiated many times and has its own namescope so any ElementName binding you create inside a DataTemplate will look inside the template for another element with that name.
Looking at it with Snoop we can also see that a RelativeSource binding can't be used directly since they are in different parts of the Visual Tree
The only thing that I can think of to get around this is to bind both of the CheckBoxes to a common ancestor, e.g. the parent DataGrid and use an attached property or the Tag property. Example
<Style TargetType="CheckBox" x:Key="InnerBox">
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="IsChecked" Value="False" />
<Style.Triggers>
<DataTrigger Value="True"
Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}},
Path=Tag}">
<Setter Property="IsChecked" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
and
<DataTemplate>
<!-- header check -->
<CheckBox Name="HeaderCheckbox"
IsChecked="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}},
Path=Tag,
Mode=OneWayToSource}"/>
</DataTemplate>
I don't think regular DataBinding to the HeaderCheckBox is possible because the CheckBox exists as part of a Template, and it is in a different branch of the VisualTree than the DataGridItems
Usually I make it the reverse: When the header CheckBox gets checked, check all the row CheckBoxes. My main reason for this is because the CheckBoxes are usually there so users can check/uncheck them, and if they're bound to the header CheckBox checked state, then the user can't alter them.
For implementing that, I usually hook into the Click or Checked event of the Header CheckBox.
If the row CheckBox.IsChecked state is bound to something in a ViewModel, I'll hook the event to a Command in my ViewModel, and set the data item that the CheckBox.IsChecked is bound to to true/false depending on the header CheckBox state (usually passed in as a CommandParameter)
If the CheckBox.IsChecked state is not bound to anything, you can use regular code-behind to loop through your DataGrid.Items, use the ItemContainerGenerator to get the ItemContainer for each item, find the CheckBox, and then set it's check state.

Conditionally changing the Foreground of a WPF ComboBox item depending upon a value in the databound ItemsSource item

I have a WPF ComboBox bound to a Collection List<Users>. I have applied a DataTemplate to show the FirstName using a TextBlock and this works as expected:
<ComboBox Margin="5" ItemsSource="{Binding Path=TheUsers}" Name="cboUsers">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Margin="10" Text="{Binding Path=FirstName}">
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>`
I have an item in my User class called IsActive which is a Boolean value. If true then I want to set the Foreground of the TextBlock to Navy.
I have spent so much time on what should be so easy and looked all over the web but most articles talk about changing the overall colour or binding to another element in the xaml.
I tried implementing a DataTrigger and after an hour removed the code because it was not working. It would not recognise my field name. Does anyone have a very simple guide to how to do this or what would be the best approach?
As you apparently are not dealing with fields after all, this style should do what you want:
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsActive}" Value="True">
<Setter Property="Foreground" Value="Navy"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
It would not recognise my field name.
You cannot bind to fields, end of story.

Storyboard for a DataTemplate element in Silverlight

I have a DataGrid in my application, which have a column with defined CellTemplate with a text block and a button in it.
I want to show the button only when hover this specific cell. How can i achieve this?
Thanks in advance.
It may be possibly to script a templated storyboard that references other templated items, but I would be too terrified to try it that way :)
If you create the cell contents as a usercontrol (with a text box and button), the animation storyboards are then easily authored for that one control and run via attached ControlStoryboardAction behaviours (I can whip one up in minutes if you need an example).
The control properties for the text box etc need to expose both values and changes (e.g. by implementing them as INotifyPropertyChanged properties or even DependencyProperties), but then you can simply bind the CellTemplate to the child control instead of a TextBox.
Hope this helps.
Here's a solution which uses triggers.
<DataGrid>
<DataGrid.Items>
<System:String>hello</System:String>
<System:String>world</System:String>
</DataGrid.Items>
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid x:Name="MyGrid" Background="Transparent">
<StackPanel>
<TextBlock Text="{Binding}"/>
<Button x:Name="MyButton" Visibility="Hidden" Content="{Binding}"/>
</StackPanel>
</Grid>
<DataTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True" SourceName="MyGrid">
<Trigger.Setters>
<Setter TargetName="MyButton" Property="Visibility" Value="Visible"/>
</Trigger.Setters>
</Trigger>
</DataTemplate.Triggers>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>

Is it possible to highlight a ComboBoxItem, but not set it as the selected item?

I have a ComboBox that I am populating with a list of parts for a Return Authorization receiving app. In our RA system, a customer can specify a return for a Kit, but only actually return part of the kit. So because of this, my ComboBox shows a list of parts that belong to the kit, and asks the receiver to choose which part was actually received.
I have found the defaulting the selected item in my received part list to the part specified in the return makes to lazy receivers, and incorrect part information being received. So I have left the ComboxBox unselected.
What I would like to do is to highlight the specified part in the ComboBox, without actually selecting it. This way the actual part can be quickly found, while still requiring the user to actually choose it.
Although, this doesn't work, I think it will illustrate what I would LIKE to do:
<ComboBox Grid.Column="1" ItemsSource="{Binding Path=Part.MasterPart.FamilyParts}"
SelectedItem="{Binding Path=ReceivedPart, ValidatesOnDataErrors=True}" >
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Part.MaxId}"
Value="{Binding Path=Part.MaxId}">
<Setter Property="Background" Value="LightSalmon" />
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
You've got the right idea. The only thing I see wrong with your code is the DataTrigger attributes.
If Value was, well, just a value, it would work:
<DataTrigger Binding="{Binding Path=Part.MaxId}" Value="999" >
I would wrap this logic up into a new property on the viewmodel for simplicity:
<DataTrigger Binding="{Binding Path=Part.ShouldHighlight}" Value="true">
There are ways to highlight other than setting the background color, and I'd recommend you explore them because it can be confusing to users to have different background colors for different reasons (selection versus highlighting). For example, you could put a little star next to relevant items or make them bold.
That said, You can just do this to set the background color of the ComboBoxItem:
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="Background" Value="{Binding Part.MaxId, Converter={StaticResource BackgroundConverter}}"/>
</Style>
</ComboBox.ItemContainerStyle>
Even better, use a view model and just bind directly to a BackgroundColor property:
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="Background" Value="{Binding BackgroundColor}"/>
</Style>
</ComboBox.ItemContainerStyle>
Instead of adding a property to the view model, you could use a Style Selector (http://msdn.microsoft.com/en-us/library/system.windows.controls.styleselector.aspx) to determine which style to use for an item.
You're on the right track, but what I would do is put a bool read-only property in your Part class that told the combobox whether it should be highlighted in this instance. You could try something like this:
<ComboBox Grid.Column="1" ItemsSource="{Binding Path=Part.MasterPart.FamilyParts}"
SelectedItem="{Binding Path=ReceivedPart, ValidatesOnDataErrors=True}" >
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid>
<Border Background="LightSalmon" Visibility="{Binding Part.Highlighted, Converter={StaticResource BoolToVizConverter}}"/>
<TextBlock Text="{Binding Part.Name}"/>
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
This way, the background of the border wouldn't display at all if Highlighted were false.

How can I change styles of XAML file using Data Binding?

I'm binding a collection of objects to a listbox in WPF, for simplicity we'll say the object i'm binding has 3 propertys: Name, URL, IsBold. What I want to do, is have it displayed different if the IsBold is set to true, again as an example I want to set the TextBlock that Name appears in, to be bold. Is something like this even possible? Can I use a different style or something if one of my properties is a certain value? (can I do anything like an if/else in XAML)? I really have no idea where to start with this.
Say I have this in my DataTemplate
<TextBlock Style="{StaticResource notBold}" Text="{Binding Path=Name}"></TextBlock>
And if the IsBold is set to true for that particular Item I would like it to be (notice the style changes from 'notBold' to 'isBold')
<TextBlock Style="{StaticResource isBold}" Text="{Binding Path=Name}"></TextBlock>
Or something similar. I guess the more general question. Is it possible to change the apperance of something based on an item that's databound? And if it's not possible, how would something like this be done? Thru the code-behind somehow?
Thanks
What you'd normally do is write a DataTemplate for the objects in the list and then have a DataTrigger set the Fontweight of the TextBlock/TextBox based on the IsBold Property.
<DataTemplate DataType="DataItem">
<TextBlock x:Name="tb" Text="{Binding Name}"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsBold}" Value="true">
<Setter TargetName="tb" Property="FontWeight" Value="Bold" />
</DataTrigger>
<DataTrigger Binding="{Binding IsBold}" Value="false">
<Setter TargetName="tb" Property="FontWeight" Value="Normal" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
You'd then set a list of DataItems to the ItemsSource property of your ComboBox (either by Databinding or directly in the codebehind myComboBox.ItemsSource=myDataItems). The rest is done by WPF for you.

Resources