Requirements: We have TextBox and when it is empty and loses focus, textbox will mark itself as "need to be filled" and raise popup.
Implementation: So far i became with following solution:
EmptyTextValidationRule actually check if textbox is empty and raise ValidationResult.
TextBox's style in XAML which responsible to to UI job (i.e. mark red color around textbox and raise popup.
Important NOTE: i cannot afford usage of code behind since this should be generic for ant textbox.
The problem is that my code is not working when textbox is empty and looses its focus.
<Window.Resources>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel>
<Grid DockPanel.Dock="Right" Width="16" Height="16" VerticalAlignment="Center" Margin="3 0 0 0">
<Ellipse Width="16" Height="16" Fill="Red" />
<Ellipse Width="3" Height="8" VerticalAlignment="Top" HorizontalAlignment="Center" Margin="0 2 0 0" Fill="White" />
<Ellipse Width="2" Height="2" VerticalAlignment="Bottom" HorizontalAlignment="Center" Margin="0 0 0 2" Fill="White" />
</Grid>
<Border BorderBrush="Red" BorderThickness="2" CornerRadius="2">
<AdornedElementPlaceholder />
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}" />
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<TextBox Height="20" Width="100" />
<TextBox Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Left" MinWidth="150" MaxWidth="180" Margin="5,0">
<TextBox.Text>
<Binding Path="IncidentName" Mode="TwoWay" NotifyOnSourceUpdated="True" UpdateSourceTrigger="LostFocus">
<Binding.ValidationRules>
<validators:EmptyTextValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</Grid>
EmptyTextValidationRule:
public class EmptyTextValidationRule : ValidationRule
{
private string m_errorText;
private const string TEXT_REQUERIED = "Text filed should not be empty";
public EmptyTextValidationRule()
{
}
public string ErrorText
{
get { return m_errorText; }
set
{
m_errorText = value;
}
}
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
string enterendText = value.ToString();
if (string.IsNullOrEmpty(enterendText))
{
if (string.IsNullOrEmpty(ErrorText))
return new ValidationResult(false, TEXT_REQUERIED);
else
return new ValidationResult(false, ErrorText);
}
return new ValidationResult(true, null);
}
}
This is not an attempt to answer your question, instead just pointing out a mistake in your code:
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
string enterendText = value.ToString(); //<< You'll get error here if value is null
if (string.IsNullOrEmpty(enterendText)) //<< No point checking here... too late!
{
if (string.IsNullOrEmpty(ErrorText))
return new ValidationResult(false, TEXT_REQUERIED);
else
return new ValidationResult(false, ErrorText);
}
return new ValidationResult(true, null);
}
Using this code, if value was ever null, then you'd get an error in the first if statement condition... you should do something like this instead:
string enterendText = string.Empty;
if (value == null || (enterendText = value.ToString()).Trim() == string.Empty)
{
...
}
Related
When a particular radiobutton is checked/clicked on I want to write some specific text for each of those radiobuttons in a particular textblock. How do I do that following the MVVM pattern ?
In my main viewmodel I have the following code...
public ICommand MyCommand { get; set; }
private string _txt;
public string Txt
{
get
{
return _txt;
}
set
{
_txt = value;
OnPropertyChanged("Txt");
}
}
private bool canexecutemethod(object parameter)
{
if (parameter != null)
{
return true;
}
else
{
return false;
}
}
private void executemethod(object parameter)
{
//what to do here;
}
public MainViewModel()
{
MyCommand = new RelayCommand(executemethod);
}
Also in the xaml:
<RadioButton VerticalAlignment="Center" Margin="15" Content="Name" Command="{Binding MyCommand}"/>
<RadioButton VerticalAlignment="Center" Margin="15" Content="Age" Command="{Binding MyCommand}"/>
<RadioButton VerticalAlignment="Center" Margin="15" Content="DOB" Command="{Binding MyCommand}"/>
<TextBlock Margin="5" Height="30" Width="150" Foreground="Red" Text="{Binding Txt, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
Say when the first radiobutton is checked the textblock text should show some text like
Please enter a name
, when the second radiobutton is checked the textblock text should show some text like
Please enter a valid age/integer value
and so on...
it can be done without view model participation:
<RadioButton VerticalAlignment="Center" Margin="15" Content="Name" Name="chkName"/>
<RadioButton VerticalAlignment="Center" Margin="15" Content="Age" Name="chkAge"/>
<RadioButton VerticalAlignment="Center" Margin="15" Content="DOB" Name="chkDob"/>
<TextBlock Margin="5" Height="30" Width="150" Foreground="Red">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=chkName,Path=IsChecked}" Value="True">
<Setter Property="Text" Value="Please enter a name"/>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=chkAge,Path=IsChecked}" Value="True">
<Setter Property="Text" Value="Please enter a valid age/integer value"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
I have implemented a listview that appears as a popup list. Now I would like to add key functionalities to it, like if whenever up arrow is pressed in a text box it should select an item in my list view and if pressing of KEY_UP/DOWN is continued it should continue changing its index respectively.
This is the EditMessageTextBox and associated EditMessageTagPopup
This is the XAML code used:
<Grid x:Name="EditGrid"
Grid.Row="1"
Visibility="{Binding EditMessageControlVisibility}"
FocusManager.IsFocusScope="False"
VerticalAlignment="Center"
Grid.Column="1"
HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Border x:Name="EditMessageBorder"
Grid.Row="0"
BorderThickness="1"
CornerRadius="1"
Margin="0,10,0,0"
BorderBrush="Gray">
<Grid>
<TextBlock FontSize="16"
Margin="10,0,0,3"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Text="Edit message"
Foreground="{StaticResource brushWatermarkForeground}"
Visibility="{Binding ElementName=EditMessageTextBox, Path=Text.IsEmpty, Converter={StaticResource BooleanToVisibilityConverter}}" />
<TextBox Name="EditMessageTextBox"
Text="{Binding MessageToEdit, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
BorderBrush="Transparent"
BorderThickness="0"
Foreground="Black"
FontSize="16"
Margin="8,1,1,1"
VerticalContentAlignment="Center"
HorizontalContentAlignment="Left"
MinHeight="35"
ScrollViewer.VerticalScrollBarVisibility="Auto"
TextWrapping="Wrap"
AcceptsReturn="False"
KeyUp="OnEditMessage_KeyUp"
SpellCheck.IsEnabled="true" />
</Grid>
</Border>
<StackPanel Grid.Row="1"
Margin="0,10"
Orientation="Horizontal">
<Button Background="Transparent"
VerticalContentAlignment="Center"
Padding="5,2,5,3"
Foreground="Black"
BorderBrush="Gray"
BorderThickness="0.8"
Width="100"
materialDesign:ShadowAssist.ShadowDepth="Depth0"
Click="EditMessageCancelButton_Clicked">Cancel</Button>
<Button Name="EditMessageButton"
VerticalContentAlignment="Center"
Padding="5,2,5,3"
Background="#007a5a"
Foreground="White"
BorderBrush="#007a5a"
Margin="15,0,0,0"
materialDesign:ShadowAssist.ShadowDepth="Depth0"
BorderThickness="0.8"
IsEnabled="True"
Width="140"
Content="Save Changes"
Click="EditMessageSaveButton_Clicked" />
</StackPanel>
<Popup x:Name="EditMessageTagPopup"
AllowsTransparency="True"
IsOpen="{Binding IsOpenTagPopUp}"
StaysOpen="False"
Placement="Top"
PlacementTarget="{Binding ElementName=EditMessageTextBox}">
<Border materialDesign:ShadowAssist.ShadowDepth="Depth5"
CornerRadius="5"
Background="White"
BorderBrush="Black"
BorderThickness="0.8"
MaxHeight="200">
<ListView x:Name="EditTaggedUsers"
Focusable="True"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Source={StaticResource UserListForTag}}"
SelectionChanged="EditMessageTagList_SelectionChanged">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<Border Name="_Border"
Padding="8">
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver"
Value="True">
<Setter TargetName="_Border"
Property="Background"
Value="#FF3BD38E" />
<Setter Property="Foreground"
Value="White" />
</Trigger>
<Trigger Property="IsSelected"
Value="True">
<Setter TargetName="_Border"
Property="Background"
Value="#FF205B4B" />
<Setter Property="Foreground"
Value="White" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Grid Margin="-15,0,0,0"
Width="500">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Rectangle Grid.Column="0"
RadiusY="5"
RadiusX="5"
Height="20"
Width="20">
<Rectangle.Fill>
<ImageBrush ImageSource="{Binding ProfileImage}"
Stretch="UniformToFill" />
</Rectangle.Fill>
</Rectangle>
<TextBlock Grid.Column="1"
Text="{Binding FullName}"
Margin="-10,0,0,0" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Border>
</Popup>
</Grid>
and here is code behind:
ApplicationContext.StoredEditingMessage = (String)ApplicationContext.EditMessageText;
var messageData = ((TextBox)sender).DataContext as ChatsModel;
var EditMessagePopup = FindEditMessagePopup(MessageList);
Border EditEessageBorder = EditMessagePopup.Child as Border;
ListView EditMessageTagList = EditEessageBorder.Child as ListView;
Dispatcher?.Invoke(() =>
{
if (_contactsViewModel.GroupedChatByDate
.Find(x => messageData != null && x.MessageGuid == messageData.MessageGuid)
.IsOpenTagPopUp == false) return;
var index = _contactsViewModel.UsersListForTag.IndexOf(_contactsViewModel.UsersListForTag.FirstOrDefault(x => x.Selected == true));
switch (e.Key)
{
case Key.Up:
if (EditMessageTagList.SelectedIndex > 0)
{
EditMessageTagList.SelectedIndex -= 1;
EditMessageTagList.ScrollIntoView(EditMessageTagList.Items[EditMessageTagList.SelectedIndex]);
}
else
{
EditMessageTagList.SelectedIndex = _contactsViewModel.UsersListForTag.Count - 1;
EditMessageTagList.ScrollIntoView(EditMessageTagList.Items[EditMessageTagList.SelectedIndex]);
}
break;
case Key.Down:
if (EditMessageTagList.SelectedIndex + 1 == _contactsViewModel.UsersListForTag.Count)
{
EditMessageTagList.SelectedIndex = 0;
_contactsViewModel.UsersListForTag[index].Selected = true;
EditMessageTagList.ScrollIntoView(EditMessageTagList.Items[EditMessageTagList.SelectedIndex]);
}
else
{
EditMessageTagList.SelectedIndex += 1;
_contactsViewModel.UsersListForTag[index].Selected = true;
EditMessageTagList.ScrollIntoView(EditMessageTagList.Items[EditMessageTagList.SelectedIndex]);
}
break;
}
_contactsViewModel.UsersListForTag.ForEach(x => x.Selected = false);
if (index != -1)
{
_contactsViewModel.UsersListForTag[index].Selected = true;
}
});
I have tried adding an item in scroll into view() instead of selected index yet no update
when there is a perfect selection made this function is invoked from code behind
private void EditMessageTagList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
try
{
var messageModel = ((ListView)sender).DataContext as ChatsModel;
if (((ListView)sender).SelectedItem is UserModel selectedUserForTag)
{
// _contactsViewModel.GroupedChatByDate.Find(x => messageModel != null && x.MessageGuid == messageModel.MessageGuid) .IsOpenTagPopUp = false;
string SelectedTag = (selectedUserForTag.Id == ApplicationContext.CurrentLoggedInUserGuid) ? $"{selectedUserForTag.UserName.Replace("(you) ", "")} " : $"{selectedUserForTag.UserName} ";
_contactsViewModel.GroupedChatByDate.Find
(x => messageModel != null && x.MessageGuid == messageModel.MessageGuid)
.MessageToEdit = "#" + SelectedTag;
}
// ((ListView) sender).SelectedItem = null;
}
catch (Exception exception)
{
LoggingManager.Error(exception);
}
}
Here is screen recording regarding issue
and
Here is working functionality
The problem is that after each navigation to the next item of the ListView you want to set focus to the selection TextBox which binds to the SelectedItem. Otherwise navigating the items of a ListView with the help of the arrow keys is already the default behavior of the ListView.
The simplest solution is to capture the keyboard input using UIElement.InputBinding on the selection TextBox (which enables to handle the keys in the view model) and then
Select the next/previous item
Scroll the SelectedItem into view
Move the focus to the selection TextBox
Move the caret of the selection TextBox to the end
DataItem.cs
class DataItem
{
public string FullName { get; set; }
public DataItem(string fullName) => this FullName = fullName;
}
ViewModel.cs
class ViewModel : INotifyPropertyChanged
{
public ObservableCollection<DataItem> DataItems { get; set; }
public ICommand SelectNextCommand => new AsyncRelayCommand(SelectNextItem);
public ICommand SelectPreviousCommand => new AsyncRelayCommand(SelectPreviousItem);
private bool IsSelectedItemChangeInternal { get; set; }
private DataItem selectedDataItem;
public DataItem SelectedDataItem
{
get => this.selectedDataItem;
set
{
this.selectedDataItem = value;
OnPropertyChanged();
// Do not filter the list when the selected item was set by the user
// e.g. by using arrow keys
if (!this.IsSelectedItemChangeInternal)
{
UpdateSearchFilter();
}
}
}
private string filterKey;
public string FilterKey
{
get => this.filterKey;
set
{
this.filterKey = value;
OnPropertyChanged();
// Only apply filters when the FilterKey was changed by the user
// e.g. by editing the edit TextBox that binds to this property
if (!this.IsSelectedItemChangeInternal)
{
ApplySearchFilter();
}
}
}
public ViewModel()
{
this.DataItems = new ObservableCollection<DataItems>();
for (var index = 0; index < 100; index++)
{
this.DataItems.Add(new DataItem("name " + index.ToString());
}
}
private void ApplySearchFilter()
{
ICollectionView collectionView = CollectionViewSource.GetDefaultView(this.Games);
this.IsSelectedItemChangeInternal = true;
collectionView.Filter = item =>
string.IsNullOrWhiteSpace(this.FilterKey) || (item as DetailItem).FullName.StartsWith(this.FilterKey);
// pre-select the first match
collectionView.MoveCurrentToFirst();
this.IsSelectedItemChangeInternal = false;
}
private void UpdateSearchFilter()
{
this.IsSelectedItemChangeInternal = true;
this.FilterKey = this.SelectedDataItem.FullName;
this.IsSelectedItemChangeInternal = false;
}
private void SelectNextItem()
{
ICollectionView collectionView = CollectionViewSource.GetDefaultView(this.DataItems);
collectionView.MoveCurrentToNext();
// Loop
if (collectionView.IsCurrentAfterLast)
{
collectionView.MoveCurrentToFirst();
}
}
private void SelectPreviousItem()
{
ICollectionView collectionView = CollectionViewSource.GetDefaultView(this.DataItems);
collectionView.MoveCurrentToPrevious();
// Loop
if (collectionView.IsCurrentBeforeFirst)
{
collectionView.MoveCurrentToLast();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName);
}
}
MainWindow.xaml.cs
private void AdjustFocus_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var listView = sender as ListView;
listView.ScrollIntoView(listView.SelectedItem);
Application.Current.Dispatcher.InvokeAsync(() =>
{
Keyboard.Focus(this.EditMessageTextBox);
this.EditMessageTextBox.CaretIndex = this.EditMessageTextBox.Text.Length;
});
}
private void AdjustFocus_OnOpened(object sender, EventArgs e)
{
this.EditTaggedUsers.Focus();
}
MainWindow.xaml
<Window>
<Window.DataContext>
<ViewModel />
</Window.DataContex>
<Grid>
<TextBox x:Name="EditMessageTextBox"
Text="{Binding FilterKey}">
<TextBox.InputBindings>
<KeyBinding Key="Down"
Command="{Binding SelectNextCommand}" />
<KeyBinding Key="Up"
Command="{Binding SelectPreviousCommand}" />
</TextBox.InputBindings>
</TextBox>
<Popup IsOpen="True"
Opened="AdjustFocus_OnOpened"
StaysOpen="False"
Placement="Top"
PlacementTarget="{Binding ElementName=EditMessageTextBox}">
<ListView IsSynchronizedWithCurrentItem="True"
Height="400"
SelectedItem="{Binding SelectedDataItem}"
ItemsSource="{Binding DataItems}"
SelectionChanged="AdjustFocus_OnSelectionChanged">
<ListView.ItemTemplate>
<DataTemplate DataType="{x:Type DataItem}">
<TextBox Text="{Binding FullName}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Popup>
</Grid>
</Window>
Remarks
As the navigation is done using the CollectionView of the ItemsSource the ListView.IsSynchronizedWithCurrentItem property must be set to true. Otherwise the navigation of the CollectionView won't effect the view.
I encounter a problem that I am not sure how to fix. I have "AddAccount" (Button), "AccountsTextBlock" and a ListBox on the main window. If not user is added, the list box will be hidden, once user is added, it will show the ListBox, AddAccount and AccountsTextBlock on the same screen, but AddAccount and AccountsTextBlock will be at different positions from when there's no user. The problem is that AddAccount and AccountsTextBlock didn't show, but a dotted rectangle shows.
<Grid>
<Canvas>
<TextBlock x:Name="AddAccountTextBlock" HorizontalAlignment="Left" TextWrapping="Wrap" VerticalAlignment="Top" Height="60" Width="369" Text="Add account to allow for simple access to your company resources
" SnapsToDevicePixels="True" Canvas.Left="38" Canvas.Top="313"/>
<Button x:Name="AddAccount" Content="Add Account" Canvas.Left="145" Canvas.Top="333" HorizontalAlignment="Left" VerticalAlignment="Top" Width="140" RenderTransformOrigin="0.466,0.977" IsCancel="True" Height="40" Foreground="White" Background="Blue" Click="OnAddAccount"/>
<TextBlock x:Name="AccountsTextBlock" Canvas.Left="176" TextWrapping="Wrap" Text="Accounts" Canvas.Top="10" Height="33" Width="68" FontSize="16" FontFamily="Tahoma"/>
</Canvas>
<ListBox x:Name="accounts" HorizontalAlignment="Left" Height="237" VerticalAlignment="Top" Width="444" SelectionChanged="ListBox_SelectionChanged" Background="White" BorderBrush="White" Margin="0,47,0,0" RenderTransformOrigin="0.5,0.5">
<ListBox.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform AngleX="0.384"/>
<RotateTransform/>
<TranslateTransform X="-0.794"/>
</TransformGroup>
</ListBox.RenderTransform>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Height" Value="100"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border Name="_Border" BorderBrush="Gray" BorderThickness="0.5"
Padding="2"
SnapsToDevicePixels="true">
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter TargetName="_Border" Property="Background" Value="LightSkyBlue"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image HorizontalAlignment="Left" VerticalAlignment="Center" Grid.Row="0" Grid.Column="0" Width="100" Source="{Binding Path=imagePath}" />
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Row="0" Grid.Column="1" Text="{Binding Path=userInfo}"></TextBlock>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
public MainWindow()
{
InitializeComponent();
using (RegistryKey key = Registry.CurrentUser.CreateSubKey(authenticatorRegistryKey))
{
Object valueStr = Registry.GetValue(authenticatorRegistryKey, addedUsersRegistryValueName, null);
if (valueStr == null)
{
//no users added yet, hide list box and move add button
accounts.Visibility = Visibility.Collapsed;
AccountsTextBlock.Visibility = Visibility.Collapsed;
Canvas.SetLeft(AddAccountTextBlock, (Application.Current.MainWindow.Width - AddAccountTextBlock.Width) / 2);
Canvas.SetTop(AddAccountTextBlock, (Application.Current.MainWindow.Height - AddAccountTextBlock.Height) / 2 - AddAccount.Height + 10);
Canvas.SetLeft(AddAccount, (Application.Current.MainWindow.Width - AddAccount.Width) / 2);
Canvas.SetTop(AddAccount, (Application.Current.MainWindow.Height - AddAccount.Height) / 2);
}
else
{
//show the added user
List<UserInfo> items = new List<UserInfo>();
items.Add(new UserInfo()
{
userInfo = "abcdefg",
imagePath = ""
});
accounts.ItemsSource = items;
AccountsTextBlock.Visibility = Visibility.Visible;
accounts.Visibility = Visibility.Visible;
}
}
}
private void OnAddAccount(object sender, RoutedEventArgs e)
{
SignInUrlWindow signInUrlWindow = new SignInUrlWindow();
signInUrlWindow.Left = Application.Current.MainWindow.Left;
signInUrlWindow.Top = Application.Current.MainWindow.Top;
signInUrlWindow.Width = Application.Current.MainWindow.Width;
signInUrlWindow.Height = Application.Current.MainWindow.Height;
signInUrlWindow.ShowDialog();
List<UserInfo> items = new List<UserInfo>();
items.Add(new UserInfo()
{
userInfo = "abcdefg",
imagePath = ""
});
accounts.ItemsSource = items;
accounts.Visibility = Visibility.Visible;
AddAccount.Visibility = Visibility.Visible;
AccountsTextBlock.Visibility = Visibility.Visible;
}
I have a control template that I've defined for a DevExpress TextEdit control and I want to change the Image Source property in the template depending on a binding (e.g. IsIncrease).
<ControlTemplate x:Key="WarningTextEdit" TargetType="dxe:TextEdit">
<Grid>
<TextBox Text="{TemplateBinding Text}"/>
<Image Margin="0,0,5,0"
Source="pack://application:,,,/DevExpress.Xpf.Core.v17.2;component/Core/ConditionalFormatting/Images/IconSets/Symbols3_2.png"
Width="17"
Height="16"
RenderOptions.BitmapScalingMode="NearestNeighbor"
HorizontalAlignment="Right"/>
</Grid>
</ControlTemplate>
If the property IsIncrease was set to true then one particular icon should be shown and if the property was set to false then another particular icon should be shown. Anyone know how to do this?
Thanks
You can do it using a converter, you have two choices, either you include 2 Images inside your Grid and you hide/show them using the converter or you use one Image and use the converter to change the Source property (which is probably a better solution). Here's a converter that can handle both situation, followed by both solutions.
BoolConverter :
public class BoolConverter : MarkupExtension, IValueConverter
{
public object TrueValue { get; set; } = Binding.DoNothing;
public object FalseValue { get; set; } = Binding.DoNothing;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is bool))
return Binding.DoNothing;
return (bool)value ? TrueValue : FalseValue;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == TrueValue)
return true;
if (value == FalseValue)
return false;
return Binding.DoNothing;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
MainWindow.xaml (solution 1 : two Images) :
<ControlTemplate x:Key="WarningTextEdit" TargetType="dxe:TextEdit">
<Grid>
<TextBox Text="{TemplateBinding Text}"/>
<Image Margin="0,0,5,0"
Source="pack://application:,,,/DevExpress.Xpf.Core.v17.2;component/Core/ConditionalFormatting/Images/IconSets/Symbols3_2.png"
Width="17"
Height="16"
RenderOptions.BitmapScalingMode="NearestNeighbor"
HorizontalAlignment="Right"
Visibility="{Binding IsIncrease, Converter={local:BoolConverter TrueValue=Collapsed, FalseValue=Visible}}"/>
<Image Margin="0,0,5,0"
Source="pack://application:,,,/DevExpress.Xpf.Core.v17.2;component/Core/ConditionalFormatting/Images/IconSets/TrafficLights3_1.png"
Width="17"
Height="16"
RenderOptions.BitmapScalingMode="NearestNeighbor"
HorizontalAlignment="Right"
Visibility="{Binding IsIncrease, Converter={local:BoolConverter TrueValue=Visible, FalseValue=Collapsed}}"/>
</Grid>
</ControlTemplate>
MainWindow.xaml (solution 2 : one Image) :
<ControlTemplate x:Key="WarningTextEdit" TargetType="dxe:TextEdit">
<Grid>
<TextBox Text="{TemplateBinding Text}"/>
<Image Margin="0,0,5,0"
Source="{Binding IsIncrease,
Converter={local:BoolConverter TrueValue='pack://application:,,,/DevExpress.Xpf.Core.v17.2;component/Core/ConditionalFormatting/Images/IconSets/Symbols3_2.png',
FalseValue='pack://application:,,,/DevExpress.Xpf.Core.v17.2;component/Core/ConditionalFormatting/Images/IconSets/TrafficLights3_1.png'}}"
Width="17"
Height="16"
RenderOptions.BitmapScalingMode="NearestNeighbor"
HorizontalAlignment="Right"/>
</Grid>
</ControlTemplate>
To achieve that you need 2 properties to store source for image.I'm gonna use Tag property and write an attached property to store 2 image source and use trigger to change the source
public class AttachedProperty
{
public static readonly DependencyProperty AltSourceProperty =
DependencyProperty.RegisterAttached("AltSource",
typeof(string), typeof(AttachedProperty),
new PropertyMetadata());
public static string GetAltSource(DependencyObject obj)
{
return (string)obj.GetValue(AltSourceProperty);
}
public static void SetAltSource(DependencyObject obj, string value)
{
obj.SetValue(AltSourceProperty, value);
}
}
ControlTemplate
<ControlTemplate x:Key="WarningTextEdit" TargetType="dxe:TextEdit">
<Grid>
<TextBox Text="{TemplateBinding Text}" />
<Image x:Name="image"
Width="17"
Height="16"
Margin="0,0,5,0"
HorizontalAlignment="Right"
RenderOptions.BitmapScalingMode="NearestNeighbor" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsIncrease " Value="True">
<Setter TargetName="image" Property="Source" Value="{Binding Path=Tag, RelativeSource={RelativeSource TemplatedParent}}" />
</Trigger>
<Trigger Property="IsIncrease " Value="False">
<Setter TargetName="image" Property="Source" Value="{Binding Path=(local:AttachedProperty.AltSource), RelativeSource={RelativeSource TemplatedParent}}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<dxe:TextEdit Tag="Image1.jpg" local:AttachedProperty.AltSource="Image2.jpg"/>
So with a lot of looking around I am hoping to make a GroupBox that acts like a Radio button. The header section would act as the bullet. I took some code from this question
Styling a GroupBox
that is how I want it to look. But I want to have it as a Radio button. So I put in this code (mind you I've only been doing WPF for a week or 2 now)
<Style TargetType="{x:Type RadioButton}" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type RadioButton}">
<BulletDecorator>
<BulletDecorator.Bullet>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border x:Name="SelectedBorder"
Grid.Row="0"
Margin="4"
BorderBrush="Black"
BorderThickness="1"
Background="#25A0DA">
<Label x:Name="SelectedLabel" Foreground="Wheat">
<ContentPresenter Margin="4" />
</Label>
</Border>
<Border>
</Border>
</Grid>
</BulletDecorator.Bullet>
</BulletDecorator>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="true">
<Setter TargetName="SelectedBorder" Property="Background" Value="PaleGreen"/>
<Setter TargetName="SelectedLabel"
Property="Foreground"
Value="Black" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I have a feeling that I can add a label to the second row of my grid, but then I don't know how to access it. I have that template in a test project in the Window.Resources section (I plan on moving it to a resource dictionary in my main project)
the xaml for my window is this
<Grid>
<GroupBox Name="grpDoor" Margin ="8" Grid.Row="0" Grid.Column="0">
<GroupBox.Header>
WPF RadioButton Template
</GroupBox.Header>
<StackPanel Margin ="8">
<RadioButton FontSize="15" Content="Dhaka" Margin="4" IsChecked="False"/>
<RadioButton FontSize="15" Content="Munshiganj" Margin="4" IsChecked="True" />
<RadioButton FontSize="15" Content="Gazipur" Margin="4" IsChecked="False" />
</StackPanel>
</GroupBox>
</Grid>
I then hoping for something like this (not sure how I'd do it yet though)
<Grid>
<GroupBox Name="grpDoor" Margin ="8" Grid.Row="0" Grid.Column="0">
<GroupBox.Header>
WPF RadioButton Template
</GroupBox.Header>
<StackPanel Margin ="8">
<RadioButton FontSize="15"
Content="Dhaka"
Margin="4"
IsChecked="False">
<RadioButton.Description>
This is a description that would show under my Header
</RadioButton.Description>
</RadioButton>
<RadioButton FontSize="15"
Content="Munshiganj"
Margin="4"
IsChecked="True">
<RadioButton.Description>
This is a description that would show under my Header
</RadioButton.Description>
</RadioButton>
<RadioButton FontSize="15"
Content="Gazipur"
Margin="4"
IsChecked="False">
<RadioButton.Description>
This is a description that would show under my Header
</RadioButton.Description>
</RadioButton>
</StackPanel>
</GroupBox>
</Grid>
Based on your clarification, here is a very simple example with a RadioButton that looks like a GroupBox.
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:SimpleViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type local:SimpleOption}">
<RadioButton GroupName="choice" IsChecked="{Binding Path=IsSelected, Mode=TwoWay}">
<RadioButton.Template>
<ControlTemplate TargetType="{x:Type RadioButton}">
<GroupBox x:Name="OptionBox" Header="{Binding Path=DisplayName, Mode=OneWay}">
<TextBlock Text="{Binding Path=Description, Mode=OneWay}"/>
</GroupBox>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Path=IsSelected, Mode=OneWay}" Value="True">
<Setter TargetName="OptionBox" Property="Background" Value="Blue"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</RadioButton.Template>
</RadioButton>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding Path=Options, Mode=OneWay}"/>
</Grid>
</Window>
public class SimpleViewModel
{
public SimpleViewModel()
{
Options = new ObservableCollection<SimpleOption>();
var _with1 = Options;
_with1.Add(new SimpleOption {
DisplayName = "Dhaka",
Description = "This is a description for Dhaka."
});
_with1.Add(new SimpleOption {
DisplayName = "Munshiganj",
Description = "This is a description for Munshiganj.",
IsSelected = true
});
_with1.Add(new SimpleOption {
DisplayName = "Gazipur",
Description = "This is a description for Gazipur."
});
}
public ObservableCollection<SimpleOption> Options { get; set; }
}
public class SimpleOption : INotifyPropertyChanged
{
public string DisplayName {
get { return _displayName; }
set {
_displayName = value;
NotifyPropertyChanged("DisplayName");
}
}
private string _displayName;
public string Description {
get { return _description; }
set {
_description = value;
NotifyPropertyChanged("Description");
}
}
private string _description;
public bool IsSelected {
get { return _isSelected; }
set {
_isSelected = value;
NotifyPropertyChanged("IsSelected");
}
}
private bool _isSelected;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged;
public delegate void PropertyChangedEventHandler(object sender, PropertyChangedEventArgs e);
}
I'd do it with a custom attached property. That way, you can bind to it from a ViewModel, or apply it directly in XAML.
First, create a new class in your Style assembly:
public static class RadioButtonExtender
{
public static readonly DependencyProperty DescriptionProperty = DependencyProperty.RegisterAttached(
"Description",
typeof(string),
typeof(RadioButtonExtender),
new FrameworkPropertyMetadata(null));
[AttachedPropertyBrowsableForType(typeof(RadioButton))]
public static string GetDescription(RadioButton obj)
{
return (string)obj.GetValue(DescriptionProperty);
}
public static void SetDescription(RadioButton obj, string value)
{
obj.SetValue(DescriptionProperty, value);
}
}
And your style's Bullet would change so that the label is:
<Label x:Name="SelectedLabel"
Foreground="Wheat"
Content="{Binding (prop:RadioButtonExtender.Description), RelativeSource={RelativeSource TemplatedParent}} />
You could then use it in your final XAML:
<RadioButton FontSize="15"
Content="Dhaka"
Margin="4"
IsChecked="False">
<prop:RadioButtonExtender.Description>
This is a description that would show under my Header
</prop:RadioButtonExtender.Description>
</RadioButton>
As an added bonus, since you're creating the Style in a separate assembly, you can create a custom XAML namespace to make using your property easier.