I am working on an application that creates textboxes/combo boxes dynamically based on if there is a single replacement for a keyword, or multiple replacements, and adds them to a stackpanel. I have ran into an issue where if the string that is being populated into the textbox is a single digit, ie: "2" the textbox is collapsed. Here is the associated DependencyProperty and constructor for the view model:
#region Properties
public ObservableCollection<string> KeywordValueList
{
get { return (ObservableCollection<string>)GetValue(_KeywordValueListProperty); }
set { SetValue(_KeywordValueListProperty, value); }
}
private static readonly DependencyProperty _KeywordValueListProperty = DependencyProperty.Register("KeywordValueList",
typeof(ObservableCollection<string>),
typeof(KeywordControlViewModel),
new PropertyMetadata(null, null));
public string KeywordValue
{
get { return (string)GetValue(_KeywordValueProperty); }
set { SetValue(_KeywordValueProperty, value); }
}
private static readonly DependencyProperty _KeywordValueProperty = DependencyProperty.Register("KeywordValue",
typeof(string),
typeof(KeywordControlViewModel),
new PropertyMetadata(null, null));
#endregion
#region Constructors
public KeywordControlViewModel(string keyword, object keywordValue)
{
Keyword = keyword;
if (keywordValue is string)
{
KeywordValue = (string)keywordValue;
KeywordValueList = null;
}
else if (keywordValue is ICollection)
{
KeywordValue = null;
ObservableCollection<string> toSet = new ObservableCollection<string>(keywordValue as List<string>);
KeywordValueList = toSet;
}
else
{
KeywordValue = "-Not Set-";
KeywordValueList = null;
}
}
#endregion
This is the relevant portion of the xaml:
<Grid HorizontalAlignment="Stretch">
<Grid.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../Styles/ControlsStyle.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="25" />
</Grid.RowDefinitions>
<!-- Labels etc on Row 0, generating properly so omitting for space-->
<TextBox Name ="KeyWordTextBox"
Style="{StaticResource InputBoxStyle}"
Text="{Binding KeywordValue}"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="3"
Visibility="{Binding KeywordValue, TargetNullValue=Hidden}">
<TextBox.ToolTip>
<Label Content="{Binding Keyword, StringFormat='Edit value for {0}'}" />
</TextBox.ToolTip>
</TextBox>
<ComboBox Name="KeyWordComboBox"
ItemsSource="{Binding KeywordValueList}"
Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" Visibility="{Binding KeywordValueList, TargetNullValue=Hidden}"/>
</Grid>
And the style:
<Style x:Key="InputBoxStyle" TargetType="TextBox">
<Setter Property="Margin" Value="0,0,5,0" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="Height" Value="20" />
</Style>
I have debugged quite a bit and found that if I change the single digit to either a single letter, string of more than one number, or string containing both it shows the textbox as expected. Also, the Visibility value for the textbox in the debugger shows Collapsed, not Hidden -- the TargetNullValue does not seem to be causing this. In fact the textbox does not show either if I change it to Visible. This only started happening when I added the option for a combobox, prior to that the textbox generated properly with a single digit.
Can anyone offer an idea why this may be happening?
I suppose that the code doesn't work because You are binding a string value (and ObservableCollection<string> value in case of combobox) to a Visibility property.
In order to hide controls on null value:
You can write a value converter similar to the one suggested here: https://stackoverflow.com/a/2123905/232530 (but just check if value parameter is null in the Convert method)
or
You can use triggers as suggested here: How to hide the empty TextBlock?.
Please let me know if You need help when using any of those solutions.
Related
I have problem with creating xaml control. I'm writing new project in VS 2015 in universal app. I want create grid. In this grid I want to have a button. In model I specifi the column (Level) and Row.
this is my code:
<ItemsControl Grid.Row="1" ItemsSource="{Binding Path=TechnologyList}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="10*"/>
<RowDefinition Height="10*"/>
<RowDefinition Height="10*"/>
<RowDefinition Height="10*"/>
<RowDefinition Height="10*"/>
<RowDefinition Height="10*"/>
<RowDefinition Height="10*"/>
<RowDefinition Height="10*"/>
<RowDefinition Height="10*"/>
<RowDefinition Height="10*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="14*"/>
<ColumnDefinition Width="14*"/>
<ColumnDefinition Width="14*"/>
<ColumnDefinition Width="14*"/>
<ColumnDefinition Width="14*"/>
<ColumnDefinition Width="14*"/>
<ColumnDefinition Width="14*"/>
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="Control">
<Setter Property="Grid.Column" Value="{Binding Level}" />
<Setter Property="Grid.Row" Value="{Binding Row}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Name}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I get a error in line <Setter Property="Grid.Column" Value="{Binding Level}" />
The error: Exception from HRESULT: 0x8000FFFF (E_UNEXPECTED) was in edytor not in running code.
What is wrong? In "old" WPF everything was OK but in Universal App for Windows 10 I have a error.
Can anyone help me ?
As noted in the section Migration notes on the Setter.Value property page on MSDN, UWP/Windows Runtime does not support bindings in Style Setters.
Windows Presentation Foundation (WPF) and Microsoft Silverlight
supported the ability to use a Binding expression to supply the Value
for a Setter in a Style. The Windows Runtime doesn't support a Binding
usage for Setter.Value (the Binding won't evaluate and the Setter has
no effect, you won't get errors, but you won't get the desired result
either). When you convert XAML styles from WPF or Silverlight XAML,
replace any Binding expression usages with strings or objects that set
values, or refactor the values as shared {StaticResource} markup
extension values rather than Binding-obtained values.
A workaround could be a helper class with attached properties for the source paths of the bindings. It creates the bindings in code behind in a PropertyChangedCallback of the helper property:
public class BindingHelper
{
public static readonly DependencyProperty GridColumnBindingPathProperty =
DependencyProperty.RegisterAttached(
"GridColumnBindingPath", typeof(string), typeof(BindingHelper),
new PropertyMetadata(null, GridBindingPathPropertyChanged));
public static readonly DependencyProperty GridRowBindingPathProperty =
DependencyProperty.RegisterAttached(
"GridRowBindingPath", typeof(string), typeof(BindingHelper),
new PropertyMetadata(null, GridBindingPathPropertyChanged));
public static string GetGridColumnBindingPath(DependencyObject obj)
{
return (string)obj.GetValue(GridColumnBindingPathProperty);
}
public static void SetGridColumnBindingPath(DependencyObject obj, string value)
{
obj.SetValue(GridColumnBindingPathProperty, value);
}
public static string GetGridRowBindingPath(DependencyObject obj)
{
return (string)obj.GetValue(GridRowBindingPathProperty);
}
public static void SetGridRowBindingPath(DependencyObject obj, string value)
{
obj.SetValue(GridRowBindingPathProperty, value);
}
private static void GridBindingPathPropertyChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var propertyPath = e.NewValue as string;
if (propertyPath != null)
{
var gridProperty =
e.Property == GridColumnBindingPathProperty
? Grid.ColumnProperty
: Grid.RowProperty;
BindingOperations.SetBinding(
obj,
gridProperty,
new Binding { Path = new PropertyPath(propertyPath) });
}
}
}
You would use them in XAML like this:
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="local:BindingHelper.GridColumnBindingPath" Value="Level"/>
<Setter Property="local:BindingHelper.GridRowBindingPath" Value="Row"/>
</Style>
</ItemsControl.ItemContainerStyle>
For a simple workaround for absolute positioning (i.e. binding the Canvas.Left and canvas.Top properties), see this answer.
Wanted to add my experience of this BindingHelper idea from #clemens. It's a neat solution but I found that when targetting a ListViewItem the binding wouldn't access the underlying view model. After debugging it, I found that I needed to make sure the binding was relative to the ListViewItem itself and the associated .Content property to enable it to correctly link to the item's view model.
My particular use case was to set the IsTabStop property of the ListViewItem based on a view model value:
private static void BindingPathPropertyChanged(DependencyObject obj,
DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is string propertyPath)
{
var binding = new Binding
{
Path = new PropertyPath($"Content.{propertyPath}"),
Mode = BindingMode.OneWay,
RelativeSource = new RelativeSource
{
Mode = RelativeSourceMode.Self
}
};
BindingOperations.SetBinding(obj, Control.IsTabStopProperty, binding);
}
}
Hope this helps if anyone else has the problem.
I have defined following control template for my custom control.
<ControlTemplate TargetType="{x:Type local:CustomControl}">
<Grid x:Name="MainGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<local:CustomPanel x:Name="MyCustomPanel" Grid.Column="0" />
<ScrollBar Grid.Column="1" Width="20" />
</Grid>
</ControlTemplate>
Here the CustomPanel derives form Panel class. Now I cannot add the items to my CustomControl directly like this
<local:CustomControl x:Name="CControl" Grid.Row="1">
<Button/>
<Button/>
<Button/>
</local:CustomControl>
What can I do for adding the items to my custom control directly from XAML?
Use [ContentProperty(PropertyName)] on your CustomControl.
And: make sure that the content property initialized to an empty list (must not be null).
E.g.:
[ContentProperty("Items")]
public class CustomControl : UserControl
{
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register("Items", typeof(UIElementCollection), typeof(CustomControl), new UIPropertyMetadata(null)));
public UIElementCollection Items
{
get { return (UIElementCollection) GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
public CustomControl()
{
Items = new UIElementCollection();
}
}
IMPORTANT: Do not create the empty collection inside the dependency property registration, i.e. do not use this:
... new UIPropertyMetadata(new UIElementCollection())
This is considered bad practice, because you then would unintentionally create a singleton collection. Please see Collection-Type Dependency Properties for more details.
Here is a sample control that allows you to directly add content in the way that you're after.
The lines of interest here are the attribute on top of the MyCustomControl class, this tells the XAML editor which property any directly added content should be placed in.
In the XAML code the important line is the ItemsControl that's bound to the Items property, this actually displays each item.
C#
[ContentProperty("Items")]
public class MyCustomControl : Control
{
public ObservableCollection<Object> Items
{
get { return (ObservableCollection<Object>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register("Items", typeof(ObservableCollection<Object>), typeof(MyCustomControl), new UIPropertyMetadata(new ObservableCollection<object>()));
}
XAML
<Style TargetType="{x:Type local:MyCustomControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MyCustomControl}">
<ItemsControl ItemsSource="{TemplateBinding Items}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<local:MyCustomControl>
<Button />
<Button />
</local:MyCustomControl>
Description:
I have a custom content control and I am trying to enable some external settings via dependency properties. Basically it's a decorator panel with two grid rows, upper one is the header, the lower one is the content (via ContentPresenter).
There are 3 items that are bound to the template (via TemplateBinding), HeaderHeight, TextSize and Header (each of them has its dependency property of an appropriate type).
Problem:
While two of the bindings work perfectly (even in design-time), the third one does not. The FontSize="{TemplateBinding TextSize}" and the Text="{TemplateBinding Header}" bindings work, but the <RowDefinition Height="{TemplateBinding HeaderHeight}" /> does not work.
The grid splits the rows' heights 50/50, no matter which value I set the HeaderHeight property to. It does not even take the default value from the DP metadata.
Question:
What is the problem with this scenario? Why do the other two bindings work with no problems and this one behaves as if there is no binding at all?
Note:
If I set DataContext = this in the constructor and replace {TemplateBinding HeaderHeight} with {Binding HeaderHeight}, the problem disappears and it works as intended. But I'd like to know why I don't need to do the same thing with other bindings to make them work.
XAML (Themes/Generic.xaml):
<Style TargetType="local:KaiPanel">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:KaiPanel">
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="{TemplateBinding HeaderHeight}" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Border>
<TextBlock FontSize="{TemplateBinding TextSize}"
Text="{TemplateBinding Header}" />
</Border>
</Grid>
<Grid Grid.Row="1">
<Border>
<ContentPresenter />
</Border>
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Content Control (C#):
public class KaiPanel : ContentControl
{
public KaiPanel()
{
this.DefaultStyleKey = typeof(KaiPanel);
}
public static readonly DependencyProperty TextSizeProperty =
DependencyProperty.Register("TextSize", typeof(double), typeof(KaiPanel), new PropertyMetadata(15.0));
public double TextSize
{
get { return (double)GetValue(TextSizeProperty); }
set { SetValue(TextSizeProperty, value); }
}
public static readonly DependencyProperty HeaderProperty =
DependencyProperty.Register("Header", typeof(String), typeof(KaiPanel), new PropertyMetadata(""));
public String Header
{
get { return (String)GetValue(HeaderProperty); }
set { SetValue(HeaderProperty, value); }
}
public static readonly DependencyProperty HeaderHeightProperty =
DependencyProperty.Register("HeaderHeight", typeof(GridLength), typeof(KaiPanel), new PropertyMetadata(new GridLength(40)));
public GridLength HeaderHeight
{
get { return (GridLength)GetValue(HeaderHeightProperty); }
set { SetValue(HeaderHeightProperty, value); }
}
}
Custom Control usage (XAML):
<UserControl ...>
<Grid x:Name="LayoutRoot">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel x:Name="buttonsStackPanel" Grid.Column="0" VerticalAlignment="Center">
<!-- Some buttons here -->
</StackPanel>
<Grid Grid.Column="1">
<controls:KaiPanel x:Name="contentPanel">
<navigation:Frame x:Name="contentFrame" Source="KP">
<navigation:Frame.UriMapper>
<uriMapper:UriMapper>
<uriMapper:UriMapping Uri="KP" MappedUri="/Views/Kornelijepetak.xaml" />
<uriMapper:UriMapping Uri="KAI" MappedUri="/Views/KaiNetwork.xaml" />
</uriMapper:UriMapper>
</navigation:Frame.UriMapper>
</navigation:Frame>
</controls:KaiPanel>
</Grid>
</Grid>
</UserControl>
Sadly it seems what you're attempting to do requires more than just a single data binding. RowDefinition isn't a subclass of FrameworkElement, and it doesn't match any of the other criteria specified in the MSDN Silverlight data binding documentation, so it can't be used as the target of a binding.
What you want to do is possible, but unfortunately it involves a little more code.
Firstly, add a field for the main grid (I've called it mainGrid) to your KaiPanel class. Then, override the OnApplyTemplate method in this class to grab the main Grid from the template and keep a reference to it in your mainGrid field:
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
mainGrid = GetTemplateChild("LayoutRoot") as Grid;
SetHeaderRowHeight();
}
This calls a method that updates the height of the first row of the grid. That method is as follows:
private void SetHeaderRowHeight()
{
if (mainGrid != null)
{
mainGrid.RowDefinitions[0].Height = HeaderHeight;
}
}
I admit I'm not 100% sure that OnApplyTemplate is called after the DPs are set. It seems that this is the case (a quick test seemed to confirm this), but all I could find to back this up was this post on the Silverlight forums. If you find that this isn't the case, you'll need to register a PropertyChangedCallback on the HeaderHeight DP that will also call this SetHeaderRowHeight method.
See also http://forums.silverlight.net/forums/t/76992.aspx#183089.
Use RelativeSource and TemplatedParent instead:
<RowDefinition Height="{Binding RelativeSource={RelativeSource TemplatedParent},
Path=HeaderHeight}" />
Here the difference between TemplateBinding and RelativeSource TemplatedParent is explained:
WPF TemplateBinding vs RelativeSource TemplatedParent
Although somewhat experienced with writing Winforms applications, the... "vagueness" of WPF still eludes me in terms of best practices and design patterns.
Despite populating my list at runtime, my listbox appears empty.
I have followed the simple instructions from this helpful article to no avail. I suspect that I'm missing some sort of DataBind() method where I tell the listbox that I'm done modifying the underlying list.
In my MainWindow.xaml, I have:
<ListBox ItemsSource="{Binding TopicList}" Height="177" HorizontalAlignment="Left" Margin="15,173,0,0" Name="listTopics" VerticalAlignment="Top" Width="236" Background="#0B000000">
<ListBox.ItemTemplate>
<HierarchicalDataTemplate>
<CheckBox Content="{Binding Name}" IsChecked="{Binding IsChecked}"/>
</HierarchicalDataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In my code-behind, I have:
private void InitializeTopicList( MyDataContext context )
{
List<Topic> topicList = ( from topic in context.Topics select topic ).ToList();
foreach ( Topic topic in topicList )
{
CheckedListItem item = new CheckedListItem();
item.Name = topic.DisplayName;
item.ID = topic.ID;
TopicList.Add( item );
}
}
Which, by tracing through, I know is being populated with four items.
EDIT
I have changed TopicList to an ObservableCollection. It still doesn't work.
public ObservableCollection<CheckedListItem> TopicList;
EDIT #2
I have made two changes that help:
In the .xaml file:
ListBox ItemsSource="{Binding}"
In the source code after I populate the list:
listTopics.DataContext = TopicList;
I'm getting a list, but it's not automagically updating the checkbox states when I refresh those. I suspect a little further reading on my part will resolve this.
Assuming TopicList is not an ObservableCollection<T> therefore when you add items no INotifyCollection changed is being fired to tell the binding engine to update the value.
Change your TopicList to an ObservableCollection<T> which will resolve the current issue. You could also populate the List<T> ahead of time and then the binding will work via OneWay; however ObservableCollection<T> is a more robust approach.
EDIT:
Your TopicList needs to be a property not a member variable; bindings require properties. It does not need to be a DependencyProperty.
EDIT 2:
Modify your ItemTemplate as it does not need to be a HierarchicalDataTemplate
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<CheckBox Content="{Binding Name}" IsChecked="{Binding IsChecked}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
Use ObservableCollection<Topic> instead of List<Topic>
Edit
it implements INotifyCollectionChanged interface to let WPF know when you add/remove/modify items
Edit 2
Since you set TopicList in code, it should be a Dependency Property, not a common field
public ObservableCollection<CheckedListItem> TopicList {
get { return (ObservableCollection<CheckedListItem>)GetValue(TopicListProperty); }
set { SetValue(TopicListProperty, value); }
}
public static readonly DependencyProperty TopicListProperty =
DependencyProperty.Register("TopicList", typeof(ObservableCollection<CheckedListItem>), typeof(MainWindow), new UIPropertyMetadata(null));
Edit 3
To see changes in items
implement INotifyPropertyChanged interface in CheckedListItem (each setter should call PropertyChanged(this, new PropertyChangedEventArgs(<property name as string>)) event)
or derive CheckedListItem from DependencyObject, and convert Name, ID, IsChecked to dependency properties
or update them totally (topicList[0] = new CheckedListItem() { Name = ..., ID = ... })
First you dont need a HeirarchicalDataTemplate for this. Just regular DataTemplate as Aaron has given is enough.
Then you need to instantiate the TopicList ObservableCollection somewhere inside the constructor of the class. which makes the ObservableCollection alive even before you add data in to it And binding system knows the collection. Then when you add each and every Topic/CheckedListItem it will automatically shows up in the UI.
TopicList = new ObservableCollection<CheckedListItem>(); //This should happen only once
private void InitializeTopicList( MyDataContext context )
{
TopicList.Clear();
foreach ( Topic topic in topicList )
{
CheckedListItem item = new CheckedListItem();
item.Name = topic.DisplayName;
item.ID = topic.ID;
TopicList.Add( item );
}
}
Others have already made useful suggestions (use an observable collection to get list-change notification, make the collection a property rather than a field). Here are two they haven't:
1) Whenever you're having a problem with data binding, look in the Output window to make sure that you're not getting any binding errors. You can spend a lot of time trying to fix the wrong problem if you don't do this.
2) Understand the role change notification plays in binding. Changes in your data source can't and won't get propagated to the UI unless the data source implements change notification. There are two ways to do this for normal properties: make the data source derive from DependencyObject and make the bound property a dependency property, or make the data source implement INotifyPropertyChanged and raise the PropertyChanged event when the property's value changes. When binding an ItemsControl to a collection, use a collection class that implements INotifyCollectionChanged (like ObservableCollection<T>), so that changes to the contents and order of the collection will get propagated to the bound control. (Note that if you want changes to the items in the collection to get propagated to the bound controls, those items need to implement change notification too.)
I know this is really old question but I came to building custom Listbox which get the SelectedItems with built in select all / unselect all
CustomListBox
public class CustomListBox : ListBox
{
#region Constants
public static new readonly DependencyProperty SelectedItemsProperty = DependencyProperty.Register(nameof(SelectedItems), typeof(IList), typeof(CustomListBox), new PropertyMetadata(default(IList), OnSelectedItemsPropertyChanged));
#endregion
#region Properties
public new IList SelectedItems
{
get => (IList)GetValue(SelectedItemsProperty);
set => SetValue(SelectedItemsProperty, value);
}
#endregion
#region Event Handlers
private static void OnSelectedItemsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((CustomListBox)d).OnSelectedItemsChanged((IList)e.OldValue, (IList)e.NewValue);
}
protected virtual void OnSelectedItemsChanged(IList oldSelectedItems, IList newSelectedItems)
{
}
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
base.OnSelectionChanged(e);
SetValue(SelectedItemsProperty, base.SelectedItems);
}
#endregion
}
ListBoxControl.cs
public partial class ListBoxControl : UserControl
{
#region Constants
public static new readonly DependencyProperty ContentProperty =
DependencyProperty.Register(nameof(Content), typeof(object), typeof(ListBoxControl),
new PropertyMetadata(null));
public static new readonly DependencyProperty ContentTemplateProperty =
DependencyProperty.Register(nameof(ContentTemplate), typeof(DataTemplate), typeof(ListBoxControl),
new PropertyMetadata(null));
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register(nameof(Items), typeof(IList), typeof(ListBoxControl),
new PropertyMetadata(null));
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register(nameof(SelectedItems), typeof(IList), typeof(ListBoxControl),
new UIPropertyMetadata(null, OnSelectedItemsChanged));
#endregion
#region Properties
public new DataTemplate ContentTemplate
{
get => (DataTemplate)GetValue(ContentTemplateProperty);
set => SetValue(ContentTemplateProperty, value);
}
public IList Items
{
get => (IList)GetValue(ItemsProperty);
set => SetValue(ItemsProperty, value);
}
public IList SelectedItems
{
get => (IList)GetValue(SelectedItemsProperty);
set => SetValue(SelectedItemsProperty, value);
}
#endregion
#region Constructors
public ListBoxControl()
{
InitializeComponent();
}
#endregion
#region Event Handlers
private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is not ListBoxControl || e.NewValue is not IList newValue)
{
return;
}
var mylist = (d as ListBoxControl).CustomList;
foreach (var selectedItem in newValue)
{
mylist.UpdateLayout();
if (mylist.ItemContainerGenerator.ContainerFromItem(selectedItem) is ListBoxItem selectedListBoxItem)
{
selectedListBoxItem.IsSelected = true;
}
}
}
#endregion
#region Private Methods
private void CheckAll_Click(object sender, RoutedEventArgs e)
{
CustomList.SelectAll();
}
private void UncheckAll_Click(object sender, RoutedEventArgs e)
{
CustomList.UnselectAll();
}
#endregion
}
#endregion
ListBoxControl.xaml
<UserControl x:Class="UserControls.ListBoxControl"
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:UserControls"
xmlns:str="Client.Properties"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
x:Name="this">
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</UserControl.Resources>
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<local:CustomListBox x:Name="CustomList"
Grid.Row="0"
Width="250"
HorizontalAlignment="Left"
SelectionMode="Multiple"
Visibility="Visible"
MinHeight="25"
MaxHeight="400"
ItemsSource="{Binding ElementName=this, Path =Items}"
SelectedItems="{Binding ElementName=this, Path =SelectedItems,Mode=TwoWay}"
Style="{StaticResource {x:Type ListBox}}"
ScrollViewer.VerticalScrollBarVisibility="Auto">
<local:CustomListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<Trigger Property="IsSelected" Value="True" >
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
</Trigger>
<Trigger Property="IsMouseCaptureWithin" Value="true">
<Setter Property="IsSelected" Value="true" />
</Trigger>
<Trigger Property="IsMouseCaptureWithin" Value="False">
<Setter Property="IsSelected" Value="False" />
</Trigger>
</Style.Triggers>
</Style>
</local:CustomListBox.ItemContainerStyle>
<local:CustomListBox.ItemTemplate>
<DataTemplate>
<DockPanel>
<CheckBox Margin="4" IsChecked="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}},Path=IsSelected}" />
<ContentPresenter Content="{Binding .}" ContentTemplate="{Binding ElementName=this, Path = ContentTemplate, Mode=OneWay}"/>
</DockPanel>
</DataTemplate>
</local:CustomListBox.ItemTemplate>
</local:CustomListBox>
<Grid Grid.Row="1" Grid.Column="1" HorizontalAlignment="Stretch" >
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="250" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Row="0" Grid.Column="1"
Orientation="Horizontal"
HorizontalAlignment="Left">
<Button Click="CheckAll_Click"
BorderBrush="Transparent"
ToolTip="Check all">
<Button.Content>
<Image Source="CheckAll.png" Height="16" Width="16"/>
</Button.Content>
</Button>
<Button
Click="UncheckAll_Click"
BorderBrush="Transparent"
Visibility="Visible"
ToolTip="Unchecked all">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=this, Path = SelectedItems.Count}" Value="0">
<Setter Property="Button.Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
<Button.Content>
<Image Source="UncheckAll.png" Height="16" Width="16" />
</Button.Content>
</Button>
</StackPanel>
<TextBlock Grid.Row="0" Grid.Column="1"
Text="{Binding ElementName=this, Path = SelectedItems.Count, StringFormat={x:Static str:Resources.STE_LABEL_X_ITEMS_CHECKED}, Mode=OneWay}"
HorizontalAlignment="Right" TextAlignment="Right" VerticalAlignment="Center"
Foreground="White" />
</Grid>
</Grid>
</UserControl>
Now you can use that custom control in any control or page and pass any content you want
EX : ConfigView.xaml
<UserControl ..
xmlns:userControls="Client.UserControls"
..>
<userControls:ListBoxControl
ShowCheckBox="True"
MinHeight="25"
MaxHeight="400"
ScrollViewer.VerticalScrollBarVisibility="Auto"
Items="{Binding MyLists, Mode=OneWay}"
SelectedItems="{Binding SelectedMyLists,Mode=TwoWay}"
HorizontalAlignment="Left">
<userControls:ListBoxControl.ContentTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<Image Source="{Binding Icon}"/>
<TextBlock VerticalAlignment="Center" Text="{Binding Name,StringFormat=' {0}'}" />
</StackPanel>
</DataTemplate>
</userControls:ListBoxControl.ContentTemplate>
</userControls:ListBoxControl>
here we bind to the selected items and than do explicit casting to our model
ConfigViewViewModel
private IList _myLists;
public IList MyLists
{
get => _myLists;
set
{
if (_myLists == value)
{
return;
}
_myLists = value;
OnPropertyChanged(nameof(SelectedItems));
}
}
public IEnumerable<MyModel> SelectedItems => MyLists.Cast<MyModel>();
change your binding to
<ListBox ItemsSource="{Binding Path=TopicList}"
I have created a custom WPF control. The control acts as a container with various regions (so it can work like a master page).
The style for this control is loaded at runtime from a separate resource dictionary as follows:
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/MyApp.Application;component/Themes/Theme.xaml" x:Name="theme"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
My custom control's style looks as follows...
<Style TargetType="{x:Type shareduc:EditControlMaster}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type shareduc:EditControlMaster}">
<Grid>
<Grid.ColumnDefinitions></Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Border BorderBrush="{DynamicResource xxBorderBrush}"
BorderThickness="0,1,0,1" Background="White" Grid.Row="0">
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<ContentPresenter Grid.Row="0" Grid.Column="0" Grid.RowSpan="2" Margin="10" Content="{TemplateBinding Image}" />
<ContentPresenter Grid.Row="0" Grid.Column="1" Margin="2" Content="{TemplateBinding Title}" />
<ContentPresenter Grid.Row="1" Grid.Column="1" Margin="2" Content="{TemplateBinding Abstract}" />
</Grid>
</Border>
<ContentPresenter Grid.Row="1" Margin="2" Content="{TemplateBinding Content}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The problem is that this style is only loaded at Runtime. So in Design Mode my control does not have any style and does not have any size or layout. How can I give my control a default style for Design Mode?
Update:
I'm making some progress... it appears I can specify a default theme to use in a file called Themes\Generic.xaml. This works fine in a small sample project, but for some reason my VS2008 designer stays blank when I do the same thing in my actual project... Help? :(
Note that my custom control's code looks as follows:
public partial class EditControlMaster : Control
{
static EditControlMaster()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(EditControlMaster),
new FrameworkPropertyMetadata(typeof(EditControlMaster)));
}
public object Title
{
get { return (object)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title", typeof(object),
typeof(EditControlMaster), new UIPropertyMetadata());
public object Image
{
get { return (object)GetValue(ImageProperty); }
set { SetValue(ImageProperty, value); }
}
public static readonly DependencyProperty ImageProperty =
DependencyProperty.Register("Image", typeof(object),
typeof(EditControlMaster), new UIPropertyMetadata());
public object Abstract
{
get { return (object)GetValue(AbstractProperty); }
set { SetValue(AbstractProperty, value); }
}
public static readonly DependencyProperty AbstractProperty =
DependencyProperty.Register("Abstract", typeof(object),
typeof(EditControlMaster), new UIPropertyMetadata());
public object Content
{
get { return (object)GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
public static readonly DependencyProperty ContentProperty =
DependencyProperty.Register("Content", typeof(object),
typeof(EditControlMaster), new UIPropertyMetadata());
}
Through lots of poking around project files I have figured out what was wrong!
Themes\Generic.xaml contains your control's default Style. This is fine.
Your Assembly.cs file needs to contain the following attribute:
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]
Voila! The VS2008 designer works!
Did you try
public EditControlMaster()
{
DefaultStyleKey = typeof(EditControlMaster);
}
as part of the constructor?
Try moving the style to a standard place.
Add / New Item / Custom Control(WPF) / "MyDummyControl"
now place your style in "Themes/Generic.xaml" that was created
remove "MyDummyControl" files and style
remove your Theme.xaml, and MergedDictionaries
One more thing, in my experience, using DynamicResource in a style defined in themes\generic.xaml (like you did for the Border) does not work (at least, does not work always). You should consider changing that to a StaticResource lookup.