ListBox within Popup - position and binding issue - c#

Following is the code of UI and code behind. There is no additional code than this.
Issues are
ListBox is not visible when I bind value to ObservableCollection with some delay.
ListBox is not in center of the popup. Can see in Design Viewer.
I tried placing ListBox out of popup and it works well. My need is to place listbox with in popup.
MainWindow.xaml
<Window x:Class="WPFTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WPFTest"
mc:Ignorable="d"
Title="TestWPF" MinHeight="500" MinWidth="600"
WindowStyle="SingleBorderWindow" WindowStartupLocation="CenterScreen">
<Grid x:Name="mainContainer">
<Popup x:Name="popup" IsOpen="False" Margin="10" PlacementTarget="{Binding ElementName=mainContainer}"
Placement="Center" Height="300" Width="300">
<ListBox ItemsSource="{Binding InstallationSummary}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsEnabled" Value="False"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderThickness="0,0,0,1"
Margin="5" Padding="2">
<TextBlock Text="{Binding}"
TextWrapping="Wrap" Foreground="Black"/>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Popup>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using System.Windows;
namespace WPFTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
ObservableCollection<string> InstallationSummary;
public MainWindow()
{
InitializeComponent();
this.Loaded += MainWindow_Loaded;
InstallationSummary = new ObservableCollection<string>();
this.DataContext = this;
}
async private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
popup.IsOpen = true;
for (int i = 0; i < 25; i++)
{
await Task.Delay(1000);
App.Current.Dispatcher.Invoke(() =>
{
InstallationSummary.Add("New Item-" + i);
});
}
}
}
}

try converting your private variable
ObservableCollection InstallationSummary
to an public property like this
public ObservableCollection<string> InstallationSummary{ get set};
and should work fine..

Related

WPF DataGrid throws ArgumentOutOfRangeException when inserting a second row

Scenario is quite simple. A WPF DataGrid whose rows come from an ObservableCollection. Things worth noting are:
the DataGrid has a GroupStyle defined
the ObservableCollection is initially empty
rows are added at the top of the ObservableCollection (ie: with Items.Insert(0, element))
First insert is successful. The second insert crashes with
System.ArgumentOutOfRangeException: 'Index was out of range. Must be non-negative and less than the size of the collection.
The problem disappears if I remove the virtualization of the columns, OR if I remove the GroupStyle OR if I put a fixed size on the columns.
Calling CollectionViewSource.GetDefaultView(Items).Refresh() immediately after Items.Insert also resolves.
However, all the above solutions are arbitrary and it is not obvious to me why I shouls do them.
I think it is simply a DataGrid bug but I would like to understand if instead there is something fundamentally wrong that I am doing.
Following, a simplified app that reproduces the issue. Click the button twice and you'll get the exception.
MainWindow.xaml
<Window x:Class="WpfDataGridGroupsBug.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfDataGridGroupsBug"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="600"
d:DataContext="{d:DesignInstance d:Type=local:MyViewModel}"
WindowStartupLocation="CenterScreen">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="GridGroupContainerStyle.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<StackPanel>
<DataGrid
x:Name="theDataGrid"
Height="280"
RowHeight="20"
ItemsSource="{Binding Items}"
AutoGenerateColumns="False"
CanUserAddRows="False"
EnableColumnVirtualization="True"
>
<DataGrid.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource GridGroupContainerStyle}"/>
</DataGrid.GroupStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="Name" MinWidth="100" Width="*" Binding="{Binding Path=SomeValue}"/>
</DataGrid.Columns>
</DataGrid>
<Button Click="AddRows" Height="30" Content="Add Row (click me twice)"/>
</StackPanel>
</Window>
GridGroupContainerStyle.xaml
<Style x:Key="GridGroupContainerStyle" TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="true">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" FontWeight="Bold"/>
<TextBlock Text="{Binding Path=ItemCount, StringFormat=' ({0})'}" FontWeight="Bold"/>
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
MainWindow.xaml.cs
using System.Windows;
namespace WpfDataGridGroupsBug
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var viewModel = new MyViewModel();
this.DataContext = viewModel;
}
private void AddRows(object sender, RoutedEventArgs e)
{
if (DataContext is MyViewModel viewModel)
viewModel.AddRowAtTop();
}
}
}
ViewModel.cs
public class MyItemModel
{
public string SomeValue { get; set; }
}
public class MyViewModel : INotifyPropertyChanged
{
public ObservableCollection<MyItemModel> Items { get; set; }
public MyViewModel()
{
Items = new ObservableCollection<MyItemModel>();
}
public void AddRowAtTop()
{
Items.Insert(0, new MyItemModel() { SomeValue = "blah" });
}
public event PropertyChangedEventHandler PropertyChanged;
}
callstack
at System.ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument argument, ExceptionResource resource)
at System.Collections.Generic.List`1.System.Collections.IList.get_Item(Int32 index)
at System.Windows.Controls.DataGridCellsPanel.ArrangeOverride(Size arrangeSize)
at System.Windows.FrameworkElement.ArrangeCore(Rect finalRect)
at System.Windows.UIElement.Arrange(Rect finalRect)
at MS.Internal.Helper.ArrangeElementWithSingleChild(UIElement element, Size arrangeSize)
at System.Windows.Controls.ItemsPresenter.ArrangeOverride(Size arrangeSize)
at System.Windows.FrameworkElement.ArrangeCore(Rect finalRect)

binding value to template property

I have a serious problem on binding value to property.
This is my source code.
public partial class MainPage : Page,Autodesk.Revit.UI.IDockablePaneProvider
{
....
public System.Windows.Media.Brush BCol { get; set; }
.....
}
<ListBox Style = "{DynamicResource ListBoxStyle}">
...
</ListBox>
<Style x:Key="ListBoxStyle" TargetType="{x:Type ListBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBox}">
<Grid Background="{Binding BCol}">
...
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I want to bind BCol to grid background but it doesn't work.
How should I do?
Depending on where BCol is set, you might need to implement INotifyPropertyChanged interface. You also need to make sure that you are binding to the view model that is assigned to your DataContext.
You can also create a DependencyProperty. I have posted both solutions below. DependencyProperty one is probably the solution that you want.
INotifyPropertyChanged solution:
XAML
<Page x:Class="WpfTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfTest"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Title="MainPage">
<Grid>
<Grid.Resources>
<Style x:Key="ListBoxStyle" TargetType="{x:Type ListBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBox}">
<Grid Background="{Binding BCol}">
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<ListBox Style = "{DynamicResource ListBoxStyle}">
</ListBox>
</Grid>
</Page>
Code behind:
using System.ComponentModel;
using System.Windows.Controls;
using System.Windows.Media;
namespace WpfTest
{
/// <summary>
/// Interaction logic for MainPage.xaml
/// </summary>
public partial class MainPage : Page, INotifyPropertyChanged
{
private Brush bCol;
public System.Windows.Media.Brush BCol
{
get { return bCol; }
set
{
bCol = value;
RaisePropertyChanged("BCol");
}
}
public MainPage()
{
InitializeComponent();
DataContext = this; //This is a hack, you should really create a separate view model
BCol=new SolidColorBrush(Colors.Blue);
}
public event PropertyChangedEventHandler PropertyChanged= delegate { };
void RaisePropertyChanged(string name)
{
PropertyChanged(this,new PropertyChangedEventArgs(name));
}
}
}
DependencyProperty solution
XAML
<Page x:Class="WpfTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfTest"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Title="MainPage">
<Grid>
<Grid.Resources>
<Style x:Key="ListBoxStyle" TargetType="{x:Type ListBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBox}">
<Grid Background="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type local:MainPage}}, Path=BCol}">
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<ListBox Style = "{DynamicResource ListBoxStyle}">
</ListBox>
</Grid>
</Page>
Code behind
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace WpfTest
{
/// <summary>
/// Interaction logic for MainPage.xaml
/// </summary>
public partial class MainPage : Page
{
private Brush bCol;
public static DependencyProperty BColProperty =DependencyProperty.Register("BCol", typeof(Brush),typeof(MainPage));
public Brush BCol
{
get { return (Brush)this.GetValue(BColProperty); }
set { this.SetValue(BColProperty, value); }
}
public MainPage()
{
InitializeComponent();
BCol=new SolidColorBrush(Colors.Blue);
}
}
}
Hope this helps.
you need to add this to the top of your xaml with all of your xmlns's
DataContext="{Binding RelativeSource={RelativeSource Self}}"

Binding from a property of each item inside an ItemsControl to a property of an object outside the ItemsControl

I have an ItemsControl, and a Button outside the ItemsControl. Each item inside the ItemsControl has a dependency property called "MyProperty" (defined in the code-behind).
I would like to set the IsEnabled property of the Button to false when at least one of the items in the ItemsControl has the MyProperty property set to 5. (of course this is just a stupid example of a more complicated situation)
I tried by means of a data trigger, but with no luck:
XAML:
<Window x:Class="cancellami24.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style x:Key="MyStyle" TargetType="{x:Type ContentPresenter}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=MyProperty}" Value="5">
<Setter Property="IsEnabled" TargetName="MyButton" Value="False" /><!--error on TargetName-->
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<ItemsControl x:Name="MyListBox" Grid.Row="0" ItemContainerStyle="{StaticResource MyStyle}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=MyProperty, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button x:Name="MyButton" Grid.Row="1" Click="MyButton_Click"/>
</Grid>
</Window>
Code-behind:
using System.Collections.ObjectModel;
using System.Windows;
namespace cancellami24
{
public partial class MainWindow : Window
{
private readonly ObservableCollection<MyItem> myCollection = new ObservableCollection<MyItem>();
public MainWindow()
{
InitializeComponent();
myCollection.Add(new MyItem(1));
myCollection.Add(new MyItem(2));
myCollection.Add(new MyItem(3));
MyListBox.ItemsSource = myCollection;
}
private void MyButton_Click(object sender, RoutedEventArgs e)
{
myCollection[2].SetValue(MyItem.MyPropertyProperty, 5);
}
}
public class MyItem : DependencyObject
{
public static readonly DependencyProperty MyPropertyProperty = DependencyProperty.Register("MyProperty", typeof(int), typeof(MyItem));
public MyItem(int propertyValue)
{
SetValue(MyPropertyProperty, propertyValue);
}
}
}
You need custom converter to solve it
public class MyConverter : IValueConverter
{
bool flag = false;
var collection = value as ObservableCollection<MyItem>();
if(collection==null) return flag;
foreach (var item in collection)
{
if (item.MyProperty==5)
{
flag = true;
break;
}
}
return flag;
}
Add MyConverter to your App.xaml
<local:MyConverter x:key="MyConverter"/>
Xaml:
<Button x:Name="MyButton" IsEnabled="{Binding ElementName=MyListBox, Path=ItemsSource, Converter={StaticResource MyConverter}}"/>

Cannot Set Foreground For TextBox in DataTemplate in WPF

I am trying to set the Foreground property for a TextBox specified in a DataTemplate but the call doesn't work.
I have a UserControl with the following XAML:
<UserControl x:Class="TextBoxColourTest.TextFrameControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
xmlns:clrtest="clr-namespace:TextBoxColourTest"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<DataTemplate x:Key="EditModeTemplate">
<TextBox Text="Hello"></TextBox>
</DataTemplate>
<Style TargetType="{x:Type clrtest:TextFrameControl}">
<Setter Property="ContentTemplate" Value="{StaticResource EditModeTemplate}"></Setter>
</Style>
</UserControl.Resources>
</UserControl>
Then I have some XAML that makes use of the TextFrameControl:
<Window x:Class="TextBoxColourTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:textBoxColourTest="clr-namespace:TextBoxColourTest"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel>
<textBoxColourTest:TextFrameControl x:Name="TextFrameControl"></textBoxColourTest:TextFrameControl>
<Button Content="Red" Click="OnMouseUpRed"></Button>
<Button Content="Green" Click="OnMouseUpGreen"></Button>
</StackPanel>
</Grid>
</Window>
Then finally the code behind where I have event handlers for the buttons to change the foreground color:
namespace TextBoxColourTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void OnMouseUpRed(object sender, RoutedEventArgs routedEventArgs)
{
TextFrameControl.Foreground = new SolidColorBrush(Colors.Red);
}
private void OnMouseUpGreen(object sender, RoutedEventArgs routedEventArgs)
{
TextFrameControl.Foreground = new SolidColorBrush(Colors.Green);
}
}
}
When one of the color buttons is clicked then the foreground color does not change.
If I change the code so that I can alter the value of the font family or font size properties then that does work. Also, I've found that if I replace the TextBox with a TextBlock then the color does change.
Bind the Foreground property of the TextBox to that of the UserControl:
<DataTemplate x:Key="EditModeTemplate">
<TextBox Text="Hello"
Foreground="{Binding Foreground,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>
</DataTemplate>

How to put a MouseDown event in a Style?

This works:
XAML:
<Window x:Class="Test239992.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<StackPanel>
<TextBlock Tag="1" Text="Customers" MouseDown="Handle_Click"/>
<TextBlock Tag="2" Text="Appointments" MouseDown="Handle_Click"/>
</StackPanel>
</Window>
Code Behind:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace Test239992
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void Handle_Click(object sender, MouseButtonEventArgs e)
{
int id = Int32.Parse(((TextBlock)sender).Tag.ToString());
MessageBox.Show("you chose id " + id.ToString());
}
}
}
But how do I put the MouseDown event in a style, this gives me the error "Cannot find the Style Property 'MouseDown' on the type 'System.Windows.Controls.TextBlock'":
<Window x:Class="Test239992.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<Style TargetType="{x:Type TextBlock}" x:Key="ClickableTextBlockStyle">
<Setter Property="MouseDown" Value="Handle_Click" />
</Style>
</Window.Resources>
<StackPanel>
<TextBlock Tag="1" Text="Customers" Style="{DynamicResource ClickableTextBlockStyle}"/>
<TextBlock Tag="2" Text="Appointments" Style="{DynamicResource ClickableTextBlockStyle}"/>
</StackPanel>
</Window>
Try EventSetter :)
<Style TargetType="{x:Type TextBlock}" x:Key="ClickableTextBlockStyle">
<EventSetter Event="MouseDown" Handler="Handle_Click" />
</Style>
Have a look at Triggers in WPF:
http://mark-dot-net.blogspot.com/2007/07/creating-custom-wpf-button-template-in.html

Categories