how to apply a style using a converter in wpf c#? - c#

I am developing a system with voice commands that apply to a grid of parameters.
I want to apply a style to the element being edited, so that the user knows where he is vocally ...
MyView.xaml
<telerik:RadNumericUpDown Name={Binding Element[0].ID} Grid.Column="0" Name="Left" MinWidth="15" FontSize="11" Minimum="0" NumberDecimalDigits="0"
BorderThickness="0" Maximum="30"
IsInteger="True" ShowButtons="False" ShowTextBox="True"
HorizontalContentAlignment="Left" Value="{Binding Element[0].Input, Mode=TwoWay, ElementName=InputViewUserControl}" Background="Transparent" Foreground="#FF858585" />
<telerik:RadNumericUpDown Name={Binding Element[1].ID} Grid.Column="0" Name="Left" MinWidth="15" FontSize="11" Minimum="0" NumberDecimalDigits="0"
BorderThickness="0" Maximum="30"
IsInteger="True" ShowButtons="False" ShowTextBox="True"
HorizontalContentAlignment="Left" Value="{Binding Element[1].Input, Mode=TwoWay, ElementName=InputViewUserControl}" Background="Transparent" Foreground="#FF858585" />
<telerik:RadNumericUpDown Name={Binding Element[2].ID} Grid.Column="0" Name="Left" MinWidth="15" FontSize="11" Minimum="0" NumberDecimalDigits="0"
BorderThickness="0" Maximum="30"
IsInteger="True" ShowButtons="False" ShowTextBox="True"
HorizontalContentAlignment="Left" Value="{Binding Element[2].Input, Mode=TwoWay, ElementName=InputViewUserControl}" Background="Transparent" Foreground="#FF858585" />
.....i have 30 elements So...
If the user says: element one, I'd like to apply style to Element[0]
If you have an idea let me know Thanks :)

You just need one Style in a Resources section and then you need to add one bool IsSelected property to your Element class:
public bool IsSelected { get; set; } // Implement INotifyPropertyChanged interface here
<Style TargetType="{x:Type telerik:RadNumericUpDown}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter Property="Background" Value="LightGreen" />
</DataTrigger>
</Style.Triggers>
</Style>
The Style will colour the Background of the object that has an IsSelected property that is set to True. All you need to do now is to set the IsSelected property to True for the current object and set the previous object's IsSelected value to False.
Note that this Style has no x:Key value... that means that it will be implicitly set on all of your controls without you needing to set the Style on each element manually.

Related

How do I select a new ListBoxItem in C# WPF after I just inserted it automatically

I have the following problem with my calculator app which I'm doing in the MVVM pattern.
I'm redoing the Windows 10 Calculator in Standard Mode. I made an ObservableCollection of MemoryItem.
MemoryItem is a class that contains an int for the Index, a double for the value and a RelayCommand for the MemoryButtons.
Basically it looks like this and is connected to my ViewModel:
public class MemoryItem
{
public double MemoryItemValue { get; set; }
public int SelectedMemoryItemIndex { get; set; }
public RelayCommand MemoryItemChange { get; set; }
}
So I've binded the SelectedMemoryItemIndex Property to the SelectedItemIndex in WPF.
My ListBox looks like this:
<ListBox Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" Style="{StaticResource MemoryListBoxStyle}"
Visibility="{Binding MemoryVisibility}" ItemsSource="{Binding MemoryCollection}"
SelectedItem="{Binding SelectedMemoryItem}" SelectionMode="Extended" SelectedIndex="{Binding SelectedMemoryItemIndex}"
HorizontalContentAlignment="Right"/>
While the style of it looks like this:
<Style x:Key="MemoryListBoxStyle" TargetType="ListBox">
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<UniformGrid Rows="2" Margin="5">
<TextBlock Style="{StaticResource DisplayStyle}" Text="{Binding MemoryItemValue}" FontSize="20"/>
<DockPanel LastChildFill="False">
<Button Content="MC" Style="{StaticResource MemoryItemButton}"
Command="{Binding MemoryItemChange}" CommandParameter="{x:Static md:MemoryUsage.Clear}"/>
<Button Content="M+" Style="{StaticResource MemoryItemButton}"
Command="{Binding MemoryItemChange}" CommandParameter="{x:Static md:MemoryUsage.Add}"/>
<Button Content="M-" Style="{StaticResource MemoryItemButton}"
Command="{Binding MemoryItemChange}" CommandParameter="{x:Static md:MemoryUsage.Substract}"/>
</DockPanel>
</UniformGrid>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
The bindings work BUT I don't know how to have the new MemoryItem selected after Inserting the new MemoryItem and deleting the new one. Is there a better of way inserting the new item? ObservableCollection doesn't include a method to update a specific item (as far as I know).
This is the method I'm using to add the value to the MemoryItemValue and insert it in my Collection:
case MemoryUsage.Add:
if (SelectedMemoryItemIndex == -1)
{
SelectedMemoryItemIndex = 0;
}
MemoryItemValue += Eingabe1;
MemoryCollection.Insert(SelectedMemoryItemIndex +1, MItem);
MemoryCollection.RemoveAt(SelectedMemoryItemIndex);
break;
This way it worked but I always have to select the new inserted MemoryItem.
I'm thankful for ANY help provided by you.
Please keep in mind that I'm a beginner in programming and this is my first SO question ever.
Here is a post that helps answer this question.
But basically:
Create an IsSelected property on your MemoryItem class and bind ListBoxItem.IsSelected to that property.
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</ListBox.ItemContainerStyle>
When you want your new item selected, just set IsSelected to true.
IsSelected = true;
And shazam! It should work.
Here is code copied from another answer that may give you more information.
<ListBox ItemsSource="{Binding Items, Source={StaticResource ViewModel}}"
SelectionMode="Extended">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding IsItemSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ItemText}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Forgive me for leaving that example exactly as I found it.

In WPF, is it possible to represent the same ViewModel with different Views, based on some condition?

I am making a TreeView with multiple levels of information being displayed. What I am hoping to achieve, is to show a different view when the sub-tree is expanded and closed. So, when just looking at the list, I want to make a view which shows a brief status overview of my more detailed view that would be visible if you expanded that item in the list.
I know I could just create 2 viewmodels implementing the same Interface, and based on a boolean IsExpanded, I could set the ActiveViewModel to one or the other, but I was just curious if I could just have the one viewmodel and change its view based on that boolean instead to save memory.
-OR-
Alternatively, should I just put 2 StackPanels into the same View, and then bind a visibility to be inverse of each other, so only one can be shown at a time?
-CODE-
Here is my current code (Private information removed / generic representation):
Xaml:
<UserControl.Resources>
<Style x:Key="TreeViewItemStyle" TargetType="TreeViewItem">
<Setter Property="Foreground" Value="Black"/>
<Setter Property="Padding" Value="5"></Setter>
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="FontFamily" Value="{StaticResource Univers57}"/>
<Setter Property="FontSize" Value="20" />
<Setter Property="IsExpanded" Value="{Binding IsExpanded}" />
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</UserControl.Resources>
<TreeView x:Name="TreeView"
HorizontalAlignment="Stretch"
ItemsSource="{Binding ItemViewModels}"
ItemContainerStyle="{StaticResource TreeViewItemStyle}">
<TreeView.Resources>
<HierarchicalDataTemplate
DataType="{x:Type viewModel:ItemViewModel}" ItemsSource="{Binding Tasks}">
<StackPanel Orientation="Horizontal" Visibility="{Binding IsVisible}>
<Image Source="../../Images/Image.png" Height="24" Width="24"/>
<TextBlock Text="{Binding Item.Name}" />
<Button Style="{StaticResource ButtonStyle}">Dispatch</Button>
</StackPanel>
<StackPanel Orientation="Horizontal" Visibility="{Binding IsOtherVisible}>
<Image Source="../../Images/Image2.png" Height="24" Width="24"/>
<TextBlock Text="{Binding Item.Name}" />
<Button Style="{StaticResource ButtonStyle}">Dispatch</Button>
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate
DataType="{x:Type viewModel:TaskViewModel}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Task.Name}" Width="200"/>
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
Both the ItemViewModel and TaskViewModel inherit from 'TreeViewModel' which implements INotifyPropertyChanged, and has IsExpanded and IsSelected.
IsVisible on the StackPanel Binding should be set based on IsExpanded's value. (It only shows up once you expand the item. So, one stackpanel or the other should show up).
I have just had a play with the WPF Visual Tree tools in VS2015 and it looks like the IsExpanded isnt being changed when I expand/collapse the tree items. It only sets a value during creation of the viewmodels, after that it will never change - even though they physically open and close when running the program.
Have managed to find a solution.
Firstly, needed to set 2 Way Binding on the binding to IsExpanded.
Secondly, I have overridden / hidden IsExpanded by declaring it as new in the derived class.
Now when set() gets called on IsExpanded, I can make a change to the IsVisible and IsOtherVisible before sending OnPropertyChange(); Now that I can change the IsVisible's, they can fire their own OnPropertyChange() and all is good in the world again.

DataTrigger on each item's textbox in ItemsControl

I have an ItemsControl that displays a list of Labels & TextBoxes that are used for data input and a button that executes some command when pressed (using the input values):
<DataTemplate x:Key="StringParameterTemplate">
<StackPanel Name="StackPanel_Parameter"
Orientation="Horizontal">
<Label Name="ParameterLabel"
Content="{Binding ParameterLabel}"
HorizontalContentAlignment="Right"
Width="200" />
<TextBox Name="ParameterTextBlock"
Text="{Binding ParameterValue, UpdateSourceTrigger=PropertyChanged}"
Width="300"/>
</StackPanel>
</DataTemplate>
. . .
<!-- Display all parameters -->
<ItemsControl Name="ListView_Parameters"
ItemsSource="{Binding ParameterCollection, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}"
ItemTemplateSelector="{StaticResource TaskParameterTemplateSelector}"
BorderThickness="0" />
<Button Name="ExecuteTaskButton"
Content="{Binding ButtonLabel}"
Style="{StaticResource ExecuteButtonStyle}"
Command="ExecuteTask">
I would like to create a style that enables/disables the button when ANY of the parameters from ListView_Parameters is empty. Something like this:
<!-- Execute button enable / disable -->
<Style x:Key="ExecuteButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Button.IsEnabled" Value="True" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=ListView_Parameters, Path=ParameterValue}" Value="">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
You can achieve this with a single binding using a converter.
<Button Content="{Binding ButtonLabel}"
IsEnabled="{Binding Path=ItemsSource,
RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}},
Converter={local:ItemsToBooleanConverter}}" />
Then your converter takes an input of the itemssource (for example a list of objects) - and can return true if all fields you want have values, false otherwise.
The converter is mostly boilerplate, but would look like something this:
public class ItemsToBooleanConverter : MarkupExtension, IValueConverter
... but the important part would like like this, if you were using a list:
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var items = value as IList<ParameterList>;
return !(items.Any( <you check here for empty values> );
}
You'll need to be sure your parameter entry fields are bound properly to their sources so that the converter check is current.

AvalonDock - prevent anchorable pane docking in document pane

Is it possible to prevent an anchorable being docked into my documents pane? I want them to be draggable and moved around the screen, but sometimes users drag them into the documents pane which makes them look poor. Then they close the tab and I can't re-open the anchorable.
If it helps my Avalon code is below:
<avalonDock:DockingManager.Theme>
<avalonDock:VS2010Theme />
</avalonDock:DockingManager.Theme>
<avalonDock:DockingManager.DocumentHeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<StackPanel Orientation="Horizontal">
<!-- the TextBlock named Limiter is used to limit the height of the TextBlock for the workflow name. -->
<TextBlock x:Name="Limiter" TextWrapping="NoWrap" Visibility="Hidden"
TextTrimming="CharacterEllipsis">
L
</TextBlock>
<TextBlock Text="{Binding Path=Title}" VerticalAlignment="Center"
ToolTip="{StaticResource WorkflowTabItemToolTip}"
MaxHeight="{Binding ActualHeight, ElementName=Limiter}" MaxWidth="150"
TextWrapping="NoWrap" TextTrimming="CharacterEllipsis" Margin="0,0,2,0"
AutomationProperties.AutomationId="WorkflowTabTitleText"/>
<TextBlock Text=" *"
ToolTip="Has unsaved changes"
Visibility="{Binding Content.UnsavedEdits, Converter={StaticResource BoolToVis}}"
AutomationProperties.AutomationId="DirtyTabIndicator"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</avalonDock:DockingManager.DocumentHeaderTemplate>
<avalonDock:DockingManager.LayoutItemContainerStyleSelector>
<utilities1:PanesStyleSelector>
<utilities1:PanesStyleSelector.WebUIStyle>
<Style TargetType="{x:Type avalonDock:LayoutAnchorableItem}">
<Setter Property="Title" Value="{Binding Model.Title}"/>
<Setter Property="IconSource" Value="{Binding Model.IconSource}"/>
<Setter Property="Visibility" Value="{Binding Model.IsVisible, Mode=TwoWay, Converter={StaticResource BoolToVisibilityConverter}, ConverterParameter={x:Static Visibility.Hidden}}"/>
<Setter Property="ContentId" Value="{Binding Model.ContentId}"/>
<Setter Property="IsSelected" Value="{Binding Model.IsSelected, Mode=TwoWay}"/>
<Setter Property="IsActive" Value="{Binding Model.IsActive, Mode=TwoWay}"/>
</Style>
</utilities1:PanesStyleSelector.WebUIStyle>
<utilities1:PanesStyleSelector.DocumentStyle>
<Style TargetType="{x:Type avalonDock:LayoutItem}">
<Setter Property="Title" Value="{Binding Model.WorkflowName}" />
<Setter Property="IsActive" Value="{Binding Model.IsActive}" />
<Setter Property="IsSelected" Value="{Binding Model.IsActive}" />
</Style>
</utilities1:PanesStyleSelector.DocumentStyle>
</utilities1:PanesStyleSelector>
</avalonDock:DockingManager.LayoutItemContainerStyleSelector>
<avalonDock:DockingManager.LayoutItemTemplateSelector>
<utilities1:PanesTemplateSelector>
<utilities1:PanesTemplateSelector.WorkflowDesignerViewTemplate>
<DataTemplate>
<ContentControl cal:View.Model="{Binding}" IsTabStop="False" />
</DataTemplate>
</utilities1:PanesTemplateSelector.WorkflowDesignerViewTemplate>
<utilities1:PanesTemplateSelector.WebUIViewTemplate>
<DataTemplate>
<ContentControl cal:View.Model="{Binding}" IsTabStop="False" />
</DataTemplate>
</utilities1:PanesTemplateSelector.WebUIViewTemplate>
</utilities1:PanesTemplateSelector>
</avalonDock:DockingManager.LayoutItemTemplateSelector>
<avalonDock:LayoutRoot>
<avalonDock:LayoutPanel Orientation="Horizontal">
<avalonDock:LayoutDocumentPaneGroup>
<avalonDock:LayoutDocumentPane AutomationProperties.AutomationId="AvalonDocumentPane"/>
</avalonDock:LayoutDocumentPaneGroup>
<avalonDock:LayoutAnchorablePane DockWidth="800" DockMinWidth="400" AutomationProperties.AutomationId="WebUIPane"/>
<avalonDock:LayoutAnchorablePane DockWidth="225" DockMinWidth="225" AutomationProperties.AutomationId="ActivitiesPane">
<avalonDock:LayoutAnchorable Title="Activities" AutoHideWidth="225" AutoHideMinWidth="225" CanClose="False" CanHide="False">
<toolbox:ToolboxControl Name="Toolbox" AutomationProperties.AutomationId="ActivitiesToolbox"
utilities1:ToolboxItemSource.ToolboxItems="{Binding ToolboxList}" />
</avalonDock:LayoutAnchorable>
</avalonDock:LayoutAnchorablePane>
</avalonDock:LayoutPanel>
</avalonDock:LayoutRoot>
</avalonDock:DockingManager>
Although I didn't find a direct way of preventing the docking, I was able to get the basic problem fixed, namely customizing different tab headers for tool windows and document windows. My document windows show asterisk (*) in the tab header to indicate changes (just like VS), whereas the tool windows should not do so.
The solution was to use DocumentHeaderTemplateSelector and provide it with two different templates, one each for documents and tool windows. Here's the XAML:
<xcad:DockingManager.DocumentHeaderTemplateSelector>
<bd:DocumentHeaderTemplateSelector>
<bd:DocumentHeaderTemplateSelector.DocumentTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="Resources\AppIcon.ico" Margin="0,0,4,0" Width="16" />
<TextBlock Text="{Binding Title}" />
<TextBlock Text=" *" Visibility="{Binding Content.IsDirty, Converter={StaticResource BoolToVisibilityConverter}}" />
</StackPanel>
</DataTemplate>
</bd:DocumentHeaderTemplateSelector.DocumentTemplate>
<bd:DocumentHeaderTemplateSelector.ToolWindowTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Title}" />
</StackPanel>
</DataTemplate>
</bd:DocumentHeaderTemplateSelector.ToolWindowTemplate>
</bd:DocumentHeaderTemplateSelector>
</xcad:DockingManager.DocumentHeaderTemplateSelector>
The selector class is simply:
Public Class DocumentHeaderTemplateSelector
Inherits DataTemplateSelector
Public Property DocumentTemplate As DataTemplate
Public Property ToolWindowTemplate As DataTemplate
Public Overrides Function SelectTemplate(item As Object, container As System.Windows.DependencyObject) As System.Windows.DataTemplate
Dim itemAsLayoutContent = TryCast(item, Xceed.Wpf.AvalonDock.Layout.LayoutContent)
If TypeOf item Is Xceed.Wpf.AvalonDock.Layout.LayoutDocument AndAlso TypeOf DirectCast(item, Xceed.Wpf.AvalonDock.Layout.LayoutDocument).Content Is DocumentVM Then
Return DocumentTemplate
Else
Return ToolWindowTemplate
End If
End Function
End Class
Now my tool windows do not show asterisk (*) and icon even if they are moved into the documents pane.
Hope this helps someone down the road.
<avalonDock:LayoutRoot>
<avalonDock:LayoutPanel CanRepositionItems="False" Orientation="Vertical">
<avalonDock:LayoutAnchorablePane Name="ToolsPane" DockHeight="100" CanRepositionItems="False">
<avalonDock:LayoutAnchorable CanDockAsTabbedDocument="False"/>
</avalonDock:LayoutAnchorablePane>
</avalonDock:LayoutPanel>
</avalonDock:LayoutRoot>
In the avalonDock:LayoutAnchorable set
CanDockAsTabbedDocument
property to False.
This will disable the anchorable view to dock in the document pane.

XAML binding with rules

In XAML I can set TwoWay binding to the local settings using the following
<TextBox
Name="TextXYZ"
Text="{Binding Source={x:Static local:Settings.Default},
Path=TextXYZ,
Mode=TwoWay}" />
<CheckBox Content=""
Name="checkBox1"
IsChecked="{Binding Source={x:Static local:Settings.Default},
Path=checkBox1,
Mode=TwoWay}" />
<CheckBox Content=""
Name="checkBoxSaveSettings"
IsChecked="{Binding Source={x:Static local:Settings.Default},
Path=checkBoxSaveSettings, Mode=TwoWay}" />
Is it possible to introduce rules to the binding in XAML so that if checkBoxSaveSettings.IsChecked=true then controls will have twoway binding but if checkBoxSaveSettings.IsChecked=false then the binding mode is another option?
You can achieve what you want with DataTrigger like so:
<TextBox>
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Text" Value="{Binding Source={x:Static local:Settings.Default}, Path=TextXYZ, Mode=OneWay}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Source={x:Static local:Settings.Default}, Path=checkBoxSaveSettings, Mode=OneWay}" Value="True">
<Setter Property="Text" Value="{Binding Source={x:Static local:Settings.Default}, Path=TextXYZ, Mode=TwoWay}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
Your method however sounds somewhat confusing for the user as you can change control value but it won't take effect until some other CheckBox it ticked. I would recommend binding IsEnabled to checkBoxSaveSettings.IsChecked like so:
<TextBox
Text="{Binding Source={x:Static local:Settings.Default}, Path=TextXYZ, Mode=TwoWay}"
IsEnabled="{Binding ElementName=checkBoxSaveSettings, Path=IsChecked}"/>
Not directly, but there are options for this. Here's just one. Create a converter on your binding. For the converter parameter, pass in the checkbox checked value.
<TextBox
Name="TextXYZ"
Text="{Binding Source={x:Static local:Settings.Default},
Path=TextXYZ,
Converter={StaticResource foo},
ConverterParameter = {Binding ElementName="checkBoxSaveSettings", Path="IsChecked",
Mode=TwoWay}" />
Then create a converter called "foo" (whatever you want). Inside it, if the parameter is true, you return the value passed in. If the parameter is false, you can return whatever you want, including the Settings.Default.TextXYZ value so nothing changes.
Another possible option is to incorporate a setter on TextXYZ but only apply the passed value to the private _TextXYZ if some other condition is true. That other condition would be bound to the checkbox IsChecked. That's something that should be done in a ViewModel and not an object class, but it would work in either.

Categories