Weird behaviour on ListBox in WPF - c#

I have created an applicatin that is able to register some users with an image. To list them, I show that images inside a ListBox, and then you can click on each user image to select it, as shown in this image.
This ListBox is using my own Template:
<Style TargetType="ListBox" x:Key="TiledListBox">
<Setter Property="Margin" Value="0,0,10,0"></Setter>
<Setter Property="Padding" Value="1"/>
<Setter Property="Background" Value="White" />
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Top" />
<Setter Property="IsTabStop" Value="False" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel Width="{Binding Path=ActualWidth, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Grid}}}" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Vertical" Width="80">
<Image Source="{Binding Path=Picture}" Width="80" Height="80"></Image>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
The ListBox in XAML:
<ListBox Name="ListBoxUsers" ItemsSource="{Binding Path=ListObservableUsers, ElementName=SelectUser}" Style="{DynamicResource TiledListBox}"
ScrollViewer.HorizontalScrollBarVisibility="Hidden" MouseDoubleClick="ListBoxUsers_MouseDoubleClick" />
Code behind only fills the ListBox items using an ObservableCollection.
The problem comes here. When I try to click on a user that it's different to the first one, more than one user is selected, as shown in this other picture:
Any idea on why that can be happening?
I have to say that I'm using the same code in another Window and it works perfectly, but I can't see any difference between them...
EDIT: You can use this code behind to make the example work.
public partial class MainWindow
{
public ObservableCollection<DtoObject> ListObservableUsers { get; set; }
public MainWindow()
{
ListObservableUsers = new ObservableCollection<DtoObject>();
InitializeComponent();
Image image = new Image {Source = Extensions.LoadBitmap(new Bitmap(Properties.Resources.vaca))};
for (int i = 0; i < 4; i++)
{
DtoObject dtoObject = new DtoObject {Picture = image};
ListObservableUsers.Add(dtoObject);
}
}
}
public static class Extensions
{
public static BitmapSource LoadBitmap(Bitmap source)
{
IntPtr ip = source.GetHbitmap();
BitmapSource bs = null;
bs = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(ip,
IntPtr.Zero, Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
return bs;
}
}
public class DtoObject
{
public Image Picture { get; set; }
}
private void ListBoxUsers_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
AcceptUser();
}
private void AcceptUser()
{
if (ListBoxUsers.SelectedIndex < 0) return;
selectedUser = (DtoObject)ListBoxUsers.SelectedItem;
EventHandler handler = UserSelected;
if (handler != null)
handler(this, new EventArgs());
DialogResult = true;
Close();
}
The event goes in another Window, so I think the problem might not be here.

Related

One togglebutton controlling another togglebutton button

I have five toggle buttons
When the first toggle button is pressed, all remaining toggle buttons should be checked
The first toggle button xaml wrote:
<ToggleButton VerticalAlignment="Center" Width="230" Height="95" Command="{Binding Path=AllCommand}" CommandParameter="All" Style="{StaticResource ButtonStyle.AllButtonStyle}">
<TextBlock Text="full agreement" />
</ToggleButton>
The rest of the toglebutton xaml wrote:
<ToggleButton VerticalAlignment="Center" Width="66" Height="66" Command="{Binding Path=Agree1Command}" CommandParameter="Agree1" Style="{StaticResource ButtonStyle.check1ButtonStyle}" />
<Style x:Key="ButtonStyle.check1ButtonStyle" TargetType="ToggleButton">
<Setter Property="Background" Value="#d1d0cf" />
<Setter Property="Foreground" Value="#ffffff" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Button.Effect">
<Setter.Value>
<DropShadowEffect Color="Black" Direction="320" ShadowDepth="3" BlurRadius="5" Opacity="0.5" />
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsChecked" Value="False" />
</MultiTrigger.Conditions>
<Setter Property="Background" Value="#d1d0cf" />
</MultiTrigger>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Button.Background" Value="#332c27" />
<Setter Property="Button.Effect">
<Setter.Value>
<DropShadowEffect Color="Black" Direction="320" ShadowDepth="0" BlurRadius="0" Opacity="0" />
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
All togglebuton styles were applied equally as in the code
How can I check the other toggle button when I press the first toggle button?
be in desperate need of help
Slight modification to my above comment. Hopefully this works for you. You could try something like the following. Basically this creates a view model for both the list of toggle buttons and the toggle buttons themselves. The command for each toggle button is bound to the command on the datacontext for the ItemsControl, not the ItemsSource. The command then calls a service that invokes an event when the buttons are modified (eg; when they are all checked). The main viewmodel subscribes to this event so that it can pull the modified view models from the service.
View
<ItemsControl ItemsSource = "{binding Buttons}">
<ItemsControl.ItemTemplate>
<ToggleButton IsChecked = "{binding IsChecked}"
Text = "{binding ButtonText}"
IsChecked = "{binding Path=DataContext.ButtonClickedCommand}"/>
</ItemsControl.ItemTemplate>
</ItemsControl>
Button View Model
public class ButtonViewModel : ViewModelBase
{
private string buttonText;
public int ButtonText
{
get => buttonText;
set
{
myVar = value;
OnPropertyChanged(nameof(ButtonText));
}
}
private bool isChecked;
public bool IsChecked
{
get => isChecked;
set
{
isChecked = value;
OnPropertyChanged(nameof(IsChecked))
}
}
}
Main View Model
public class ListViewModel : ViewModelBase
{
public ObservableCollection<ButtonViewModel> Buttons { get; set; }
public ICommand ButtonClickedCommand;
private readonly ToggleButtonService buttonService;
public ListViewModel()
{
buttonService = new ToggleButtonClickedCommand(Buttons);
buttonService.ButtonChecked =+ UpdateButtons
ButtonClickedCommand = new ToggleButtonClickedCommand(buttonService);
}
private void UpdateButtons()
{
Buttons = buttonService.Buttons;
}
}
Toggle button Service
public class ToggleButtonService
{
private IEnumerable<ButtonViewModel> buttonViewModels;
public IEnumerable<ButtonViewModel> ButtonViewModels
{
get { return buttons; }
set
{
buttonViewModels = value;
ButtonsChecked?.Invoke();
}
}
public event Action ButtonChecked;
public ToggleButtonService(IEnumerable<ButtonViewModel> buttons)
{
buttonViewModels = buttons;
}
public void SelectOrUnSelectAllButtons()
{
List<ButtonViewModel> Buttons = new List<ButtonViewModel>(ButtonViewModels);
foreach(button in Buttons)
{
button.IsChecked = !button.IsChecked;
}
ButtonViewModels = Buttons;
}
}
ToggleButton Clicked Command
public class ToggleButtonClickedCommand : ICommand
{
private readonly ToggleButtonService ButtonService;
public ToggleBUttonClickedCommand(ToggleButtonService buttonService)
{
ButtonService = buttonService;
}
public event EventHandler? CanExecuteChanged;
public bool CanExecute(object? parameter)
{
return true;
}
public async void Execute(object? parameter)
{
ButonService.SelectOrUnSelectAllButtons();
}
}

Dynamic DataTemplate in ControlTemplate via Trigger

I have a listbox "listBox_Results" and several ItemTemplates(one of them ItemTemplateStyle1), in my ItemContainerStyle I'm setting Template property for item. So I want change my ItemTemplate in trigger "IsSelected".
(in common sense: I want my listboxitem change size and content display on selection, by dynamicly setting diffrent ItemTemplate)
Do you have any solutions?
Best regards
upd:If you think this question is unclear or not useful, most apreciate if tell you me why, before you minus
Code:
<ListBox Name="listBox_Results"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
BorderThickness="0"
Margin="2"
Grid.Row="0"
ItemTemplate="{StaticResource ItemTemplateStyle1}"
ItemsSource="{Binding}" >
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="{Binding Path=VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
<Setter Property="Padding" Value="2,2,2,2"/>
<Setter Property="Margin" Value="2"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border x:Name="Bd" Margin="1" SnapsToDevicePixels="true" CornerRadius="3" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" >
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Background" TargetName="Bd">
<Setter.Value>
#E1E1E1
</Setter.Value>
</Setter>
...
First, take out your inline styling and create a ResourceDictionary to keep things together. This will also help with the template switch I am suggesting.
In the Resource Dictionary, you will define the two templates that you want (the selected and unselected list item templates), the style of the list item and the list box itself. I am abbreviating the code, just to show how I would put the items together.
In the ResourceDictionary
<ControlTemplate x:Key="unselectedTemplate" TargetType="{x:Type ListBoxItem}">
<Grid>
<ContentPresenter />
</Grid>
</ControlTemplate>
<ControlTemplate x:Key="selectedTemplate" TargetType="{x:Type ListBoxItem}">
<Grid>
<ContentPresenter Margin="3"/>
</Grid>
</ControlTemplate>
<Style x:Key="listboxItemStyle" TargetType="{x:Type ListBoxItem}">
<Setter Property="Template" Value="{StaticResource unselectedTemplate}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsSelected, RelativeSource={RelativeSource Self}}" Value="True">
<Setter Property="Template" Value="{StaticResource selectedTemplate}"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="listBoxStyle" TargetType="{x:Type ListBox}">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="ItemContainerStyle" Value="{StaticResource listboxItemStyle}"/>
</Style>
Then, when you are creating your list box on the page... just reference the list box style key.
<ListBox Name="listbox_Results" Style="{StaticResource listBoxStyle}" ItemsSource="{Binding}"/>
Make sure the ControlTemplates are defined before the styles, I found when I don't I run into errors. Also, this keeps your layout page cleaner, and the styles are easier to reuse if you need to use them again.
I uploaded a very basic example here.
You have to use an Data template selector which will select a particular data template according to the conditions in your selector.
You have to write the data templates in xaml with separate names and select them from the DataTemplateSelector class file.
For that you need to inherit from the base class DataTemplateSelector
I will share some sample code with you. Please check this and you will get an idea of how to use an item template selector.
XAML :
<Window x:Class="WpfApplication5.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication5"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <DataTemplate x:Key="NormalUserDataTemplate">
            <StackPanel>
                <TextBlock Text="{Binding Name}" />
            </StackPanel>
        </DataTemplate>
        <DataTemplate x:Key="PremiumUserDataTemplate">
            <StackPanel Background="LightBlue">
                <TextBlock Text="{Binding Name}" />
            </StackPanel>
        </DataTemplate>
        <local:PremiumUserDataTemplateSelector x:Key="myPremiumUserDataTemplateSelector" />
    </Window.Resources>
    <Grid>
        <ListView x:Name="myListView" ItemTemplateSelector="{StaticResource myPremiumUserDataTemplateSelector}">
        </ListView>
    </Grid>
</Window>
Code behind :
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<User> users = new List<User>();
for (int i = 0; i < 10; ++i)
{
var user = new User { ID = i, Name = "Name " + i.ToString(), Age = 20 + i };
if (i == 2 || i == 4)
{
user.IsPremiumUser = true;
}
users.Add(user);
}
myListView.ItemsSource = users;
}
}
public class PremiumUserDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
FrameworkElement elemnt = container as FrameworkElement;
User user = item as User;
if(user.IsPremiumUser)
{
return elemnt.FindResource("PremiumUserDataTemplate") as DataTemplate;
}
else
{
return elemnt.FindResource("NormalUserDataTemplate") as DataTemplate;
}
}
}
public class User
{
public int ID { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public bool IsPremiumUser { get; set; }
}

WPF ListBox data validation

I have a ListBox of string items where I want to validate the strings every time a string is added or removed.
Below is the code I've cobbled together, but the problem is that ValidateAddresses is never called when the ObservableCollection Addresses changes.
Intended behavior is that when an invalid string is found, a red border should be shown around the ListBox with a tooltip that displays the error message.
This INotifyDataErrorInfo setup works fine for TextBoxes, so I dunno what I am doing wrong here.
ViewModel
[CustomValidation(typeof(ItemViewModel), "ValidateAddresses")]
public ObservableCollection<string> Addresses
{
get
{
return item.Addresses;
}
set
{
item.Addresses = value;
NotifyPropertyChanged(nameof(Addresses));
}
}
XAML
<Grid>
<Grid.Resources>
<Style TargetType="ListBox">
<Setter Property="Margin" Value="5"/>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate x:Name="TextErrorTemplate">
<DockPanel LastChildFill="True">
<AdornedElementPlaceholder>
<Border BorderBrush="Red" BorderThickness="2"/>
</AdornedElementPlaceholder>
<TextBlock Foreground="Red"/>
</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).CurrentItem.ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<ListBox ItemsSource="{Binding Path=Item.Addresses, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}" SelectedIndex="{Binding Path=SelectedAddress, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
Validation method (never called)
public static ValidationResult ValidateAddresses(object obj, ValidationContext context)
{
ItemViewModel item = (ItemViewModel)context.ObjectInstance;
if (item.Addresses.Count > 0)
{
foreach (string address in item.Addresses)
{
if (Regex.IsMatch(address, #"[^\w]"))
return new ValidationResult($"{address} is not a valid address.", new List<string> { "Addresses" });
}
}
return ValidationResult.Success;
}
I ended up adding following in class constructor for each ObservableCollection I had.
Addresses.CollectionChanged += (sender, eventArgs) => { NotifyPropertyChanged(nameof(Addresses)); };
I tried to look into circumstances upon which unsubscribing from events is required to prevent memory leaks, but it does not seem like this is one of these cases so cleanup shouldn't be required.
Null check is not required because ObservableCollections are all initialized in Model class constructor.
Thank you for your replies.
And this is the code for NotifyPropertyChanged.
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

wpf validation rules are not kicking in

I have a Xaml page with style property for a text box. Whenever a user clicks the button with the textbox empty I need to display the validation error. Right now even when the textbox is empty and the button is pressed, a messagebox with empty text pops up. Even the Applicatin Exception is not working.. Please help...
Xaml:
<Window.Resources>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel LastChildFill="True">
<TextBlock DockPanel.Dock="Right"
Foreground="Orange"
FontSize="12pt">
!!!!
</TextBlock>
<Border BorderBrush="Green" BorderThickness="1">
<AdornedElementPlaceholder />
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<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>
</Window.Resources>
My ViewModel Class:
public class Customer
{
private string _Text;
public string text
{
get
{
return _Text;
}
set
{
_Text = value;
if (string.IsNullOrEmpty(value))
{
throw new ApplicationException("Name is Mandatory");
}
}
}
public Customer()
{
}
private RelayCommand<object> _commandOkInstance;
public ICommand CommandOk
{
get
{
if (_commandOkInstance == null)
_commandOkInstance = new RelayCommand<object>(OkCommand);
return _commandOkInstance;
}
}
public void OkCommand(object obj)
{
MessageBox.Show(_Text);
}
}
Main Window.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Customer c = new Customer();
this.DataContext = c;
}
}

WPF Custom Control - Button Event

I start the WPF today, I'm trying to implement a custom control.
My problem is that I can not select an element in the template.
My code:
[Generic.xaml]
<Style x:Key="{x:Type local:ImageButton}" TargetType="{x:Type local:ImageButton}" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ImageButton}">
<StackPanel>
// -> My image
<Image Source="{Binding Path=Tag, RelativeSource={RelativeSource TemplatedParent}}"></Image>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
[ImageButton.cs]
public class ImageButton : Button
{
public Image imageOff { get; set; }
public Image imageOn { get; set; }
static ImageButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ImageButton), new FrameworkPropertyMetadata(typeof(ImageButton)));
}
public ImageButton()
: base()
{
this.MouseEnter += new MouseEventHandler(SetImageON);
}
public void SetImageON(object sender, MouseEventArgs e)
{
//Here i wanna change my image from StackPanel
}
}
Am I on the good way ? How can I change that image?
Give a name to your image, like x:Name="PART_Image". Then in your code behind override OnApplyTemplate method and store it into a variable.
private Image PART_Image;
public override void OnApplyTemplate()
{
PART_Image = this.GetTemplatedChild("PART_Image");
}
In this case you can access it in your method like:
this.PART_Image.Visibility = Visibility.Collapsed;
sender is your control so you could also right this.SetCurrentValue.
public void SetImageON(object sender, MouseEventArgs e)
{
ImageButton btn = (ImageButton)sender;
btn.SetCurrentValue(TagProperty,imageOn.Source);
}
it seems as though you might wan't to just save the source of your image in the control , instead of the entire image.
But if you wan't to use an Image object in code like this
xaml : in your template .
<StackPanel x:Name="myPanel" />
cs : in your control :
myPanel.Children.Add(imageOn);
You can achieve what you want with a Trigger:
<Style x:Key="{x:Type local:ImageButton}" TargetType="{x:Type local:ImageButton}" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ImageButton}">
<StackPanel>
<Image Source="{Binding Path=imageOff.Source, RelativeSource={RelativeSource TemplatedParent}}">
<Image.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Image.Source" Value="{Binding Path=imageOn.Source, RelativeSource={RelativeSource TemplatedParent}}" />
</Trigger>
</Image.Triggers>
</Image>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
That way you don't need the event in the code-behind. If you wanted to, you could make imageOff and imageOn DependencyProperties, that way you could define the source images in xaml too:
public class ImageButton : Button {
public static readonly DependencyProperty imageOffProperty = DependencyProperty.Register("imageOff", typeof(Image), typeof(ImageButton), new PropertyMetadata(null));
public Image imageOff {
get { return (Image)GetValue(imageOffProperty); }
set { SetValue(imageOffProperty, value); }
}
public static readonly DependencyProperty imageOnProperty = DependencyProperty.Register("imageOn", typeof(Image), typeof(ImageButton), new PropertyMetadata(null));
public Image imageOn {
get { return (Image)GetValue(imageOnProperty); }
set { SetValue(imageOnProperty, value); }
}
static ImageButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ImageButton), new FrameworkPropertyMetadata(typeof(ImageButton)));
}
}
So when you declare an ImageButton in xaml, you can do something like this:
<local:ImageButton imageOn="/Resource/Path/To/imageOn.png" imageOff="/Resource/Path/To/imageOff.png" />

Categories

Resources