Most of the tutorials and questions I see are about restyling the listbox to look different, but I'm interested in adding additional controls to make it behave differently. I initially started out trying to make the list builder control out of a checkbox list, but found myself too deep. I decided to abstract and start with a smaller problem.
What I am looking to do first, to get a better understanding of how this works is add "up" and "down" buttons next to the control. I think this can all be done in xaml, so to try and pressure myself to stick to that I'm working in Kaxaml.
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Page.Resources>
<!-- ListBox Order Button Style
Col 1
Listbox
Col 2
Buttons Up and Down
-->
<Style x:Key="{x:Type ListBox}" TargetType="{x:Type ListBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBox}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<Rectangle Fill="Yellow"/>
<!--<ListBox></ListBox>-->
</Grid>
<StackPanel Grid.Column="1">
<Button>Up</Button>
<Button>Down</Button>
</StackPanel>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Page.Resources>
<Grid>
<ListBox>
<TextBlock>Value 1</TextBlock>
<TextBlock>Value 2</TextBlock>
<TextBlock>Value 3</TextBlock>
<TextBlock>Value 4</TextBlock>
</ListBox>
</Grid>
</Page>
I am currently hung up on a few things.
1) When I try to use a ListBox where the Yellow Rectangle is I start getting infinite loop problems.
2) I'm not sure how to connect the buttons to the listbox once it is there. I think Triggers is the answer, but I don't have much experience with them.
Your infinite loop can be addressed by not relying on the TargetType to apply the style. Instead, apply the style explicitly via a named key (i.e. something other than {x:Type Listbox}). That way the style is applied only when you specifically want it to be applied.
"Connecting" the buttons can be done a variety of ways. The simplest would be to handle the Button.Click event and perform whatever action you want there.
All that said, I think you're going about this the wrong way. Let a ListBox be a ListBox; don't try to make it into something it's not. If you want a reusable control that adds functionality around a ListBox, like buttons to control the contents of the ListBox, you should probably be authoring a UserControl, which is essentially a composite control made up of whatever you want.
Doing so will give you a lot more control over the appearance of the control. You'll also have the opportunity to declare dependency properties on your control that are specific to exactly what that control needs to support (something you can't do just with a Style). Yes, it also means you'll have to expose properties of contained elements via new properties in your UserControl that effectively delegate to the contained elements, but that's a small price to pay for the flexibility and relative simplicity of creating the UserControl in the first place.
Related
ListView offers a DragItemsStarting event that comes with according event args. However, unlike DragStartingEventArgs - common to other elements - it does not offer a DragUI, as far as I can tell. My only option is to use DragOver event which is very annoying.
So, I instead said I'll make the ListViewItem's content draggable. However, that backfired because now the Click event doesn't get through anymore, or only very rarely. Simply put, I either can customize the DragUI and not click my ListItems, or the DragUI looks bad, but I retain my functionality.
Is it possible to get a custom DragUI and have the click be handled by the ListView?
You can still utilize DragStarting, but you have to add it to the item content itself within your DataTemplate:
<ListView>
<ListView.ItemTemplate>
<DataTemplate>
<Grid CanDrag="True" DragStarting="ItemDragStartingHandler">
...
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
If that causes the click event not working properly, you could try customizing the ListViewItem container itself, which might work. Right-click the ListView in the designer or the Document Outline window, select Edit Additional Templates, and then Edit Generated Item Container. From the last menu select Edit a Copy...
You will get a Style which might be quite long, but within it you will find the the following:
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<ListViewItemPresenter DragStarting="ItemDragStartingHandler" x:Name="Root" ...>
You can apply DragStarting event to the ListViewItemPresenter and that might satisfy your needs.
I'd like to understand which properties of an xaml Control are applied to the ControlTemplate of that Control. F.e. If I create a Control based on the Window Class like this:
(This is very simplified — It doesn't make sense in the current state I know...)
public class BaseWindow : Window {
public BaseWindow() { }
}
And the Template:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:shell="clr-namespace:Microsoft.Windows.Shell;assembly=Microsoft.Windows.Shell"
xmlns:local="clr-namespace:Arctic">
<Style TargetType="{x:Type local:BaseWindow}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:BaseWindow}">
<Grid Background="Transparent">
<Border Background="{TemplateBinding Background}"/>
<ContentPresenter/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Now, when I specify a BaseWindow Control in my app the Margin Property is applied to the BaseWindow without specifying a TemplateBinding. The Background isn't, I have to declare the TemplateBinding in the Template in order to achieve that. Can you explain to me why some properties are applied to the ControlTemplate by default and others are not?
My guess is, that the Window.xaml (Default Window Template of WPF) binds to some properties like the Margin but ignores some like Background. If that is true, then I do not understand why I can set the Background in a Window Control and it is applied to it. Seems like the Window binds to some properties and stops doing that when you derive from it…
This is probably completely wrong — I just wanted to explain my thoughts.
Window class inherit FrameworkElement and all its properties including FrameworkElement.Margin. Same goes for Control.Background. Your question is why you have to do something to have Control.Background working.
Answer is simple:
Margin is used in layouting, its functionality is implemented/provided by FrameworkElement and it happens always, invisible for you and disregarding of ControlTemplate (because all framework elements participate in layouting and use margin).
Background, in turn, is provided to be use by visuals. It's up to you how to use it, because only you know how control will looks like. Control doesn't know what to do with that property.
So, you have to use TemplateBinding to bind Background to some color in your ControlTemplate, but Margin works without need to do anything in control template.
Today I'm having trouble passing values from a parent control down to the properties of a child control in a list.
I have a custom control which I've made which functions as a Thumbnail Check Box. Essentially it's just a checkbox wrapped around an image with some nice borders. It's all wrapped up into a DLL and deployed as a custom control
If I want to use a single instance of the control, I can do so like this...
<tcb:ThumbnailCheckBox IsChecked="True"
ImagePath="D:\Pictures\123.jpg"
CornerRadius="10"
Height="{Binding ThumbnailSize}"
Margin="10" />
Code Listing 1 - Single Use
This works great, and easily binds to ThumbnailSize on my ViewModel so I can change the size of the image in the control however I want.
The problem is when I want to expand the use of this control into a list, I'm running into a few problems.
To begin, I've styled the ListBox control to meet my needs like so...
<Style TargetType="{x:Type ListBox}"
x:Key="WrappingImageListBox">
<!-- Set the ItemTemplate of the ListBox to a DataTemplate
which explains how to display an object of type BitmapImage. -->
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<tcb:ThumbnailCheckBox ImagePath="{Binding ImagePath}"
IsChecked="{Binding Selected}"
Height="{TemplateBinding utilities:MyAttachedProperties.ImageSize}"
CornerRadius="8"
Margin="10">
</tcb:ThumbnailCheckBox>
</DataTemplate>
</Setter.Value>
</Setter>
<!-- Swap out the default items panel with a WrapPanel so that
the images will be arranged with a different layout. -->
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<!-- Set this attached property to 'Disabled' so that the
ScrollViewer in the ListBox will never show a horizontal
scrollbar, and the WrapPanel it contains will be constrained
to the width of the ScrollViewer's viewable surface. -->
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility"
Value="Disabled" />
</Style>
Code Listing 2 - ListBox Style
And I call it like this from my main view...
<ListBox ItemsSource="{Binding DirectoryPictures}"
Grid.Row="1"
Style="{DynamicResource WrappingImageListBox}"
Background="Transparent"
util:MyAttachedProperties.ImageSize="500"/>
Code Listing 3 - Main Call
This works exactly as I'd like, except for the ImageSize property. Both ImagePath and Selected are properties of the individual list items being bound to the ListBox.
As you can see, I created an attached property to try to pass the value (500), but it doesn't seem to be working. I should note that I think the style I've created is correct because the elements use the default value.
public static class MyAttachedProperties
{
public static double GetImageSize(DependencyObject obj)
{
return (double)obj.GetValue(ImageSizeProperty);
}
public static void SetImageSize(DependencyObject obj, double value)
{
obj.SetValue(ImageSizeProperty, value);
}
public static readonly DependencyProperty ImageSizeProperty =
DependencyProperty.RegisterAttached(
"ImageSize",
typeof(double),
typeof(MyAttachedProperties),
new FrameworkPropertyMetadata(50D));
}
Code Listing 4 - Attached Property
The 50D specified on the last line is applying to the listed control. If I change it, and recompile, the end result changes. But the sent value of 500 I specified in my ListBox Main call (listing 3) is not ever sent. Of course, I would eventually like to change the 500 into a bound property on my view model, but I won't do that until I get it working with an explicit value.
Can someone help me figure out how to send a value from my main ListBox call (listing 3) and apply it to the individual items that are populated by the template? The other properties I have work, but they are a properties of each item in the List I'm binding to the ListBox, whereas ImageSize is not.
EDIT To address First Response
This seems to be working, but it's kind of peculiar. My listbox is now being called like so...
<ListBox ItemsSource="{Binding DirectoryPictures}"
Grid.Row="1"
Style="{DynamicResource WrappingImageListBox}"
Background="Transparent" />
And I've changed my style to the code you suggested...
<tcb:ThumbnailCheckBox ImagePath="{Binding ImagePath}"
IsChecked="{Binding Selected}"
Height="{Binding Path=DataContext.ThumbnailSize, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBox}}}"
CornerRadius="8"
Margin="10">
My only concern is, now the style is accessing the ViewModel for that control directly rather than receiving a bound value.
Suppose I wanted to use the ListBox again, but on another UserControl whose ViewModel didn't have ThumbnailSize property, but used one by another name?
You see where I'm going with this... the current solution is not very extensible and is limited to the current classes as they are named exactly.
In fact, in a perfect world, I'd like to have variable names for the ImagePath and Selected properties, but that's a different discussion.
It's possible to use FindAncestor. The idea of that is, child traverses through logical tree, and tries to find parent with concrete type (in this case, ListBox), and then accesses attached property. See http://wpftutorial.net/BindingExpressions.html for more binding expressions.
In your ItemTemplate, this is how you could access ThumbnailSize property:
{Binding Path=(util:MyAttachedProperties.ImageSize),
RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType={x:Type ListBox}}}
Essentially, the question asked here was a little bit opposite, but results are same. "How could items in ListBox access ListBox (attached) properties.
Thanks in advance for any help I can get! I'll jump right in!
Assume I have the following XAML. I left out parts that wouldn't be needed to hopefully keep this easier to read.
The TreeView is populated in the MainWindow code behind by created an ObservableCollection of MainViewModelBaseobjects. The Properties of those objects are based on a XML file read in at start by the main windows code being.
This makes the items of the TreeView of type MainViewModelBase. From there I want to use the string "Type" property (which was read in from the XML) of the SelectedItem of the Treeview to display a UserControl on the right side of the screen. Based on TONs of googling, the below code uses DataTemplates to accomplish the view switches.
My question is this. The UserControl that I want to populate needs to be be binded to another XML file, and the name of that XML file will be based on the string called Name stored in the MainViewModelBase. The code below gets the new view to show up, but I can't figure out how to get the new Views DataContext to be set to the XML. There must be some way though the content control to do this. I think that when you use a DataTemplate to do the switch, the resulting view inherits the DataContext of the ViewModel type that "created" it. But I think the DataContext isn't set until after the constructor of the view is done. Thus I can't have the constructor of the view open the XML based on the string "name" of the MainViewModel. Is there a way to have the DataContext updated after the fact? Thanks!
Another note, there is lots of items in the TreeView, thus lots of XMLs. I don't want to have all XMLs in memory at once, only when the view that needs it needs it.
Finally, I am new to WPF, so I apologize in advance if part of my above question are dumb or completely confusing. I am asking them from the perspective of someone new to the technology.
Note: I know I can bind the TreeView directly to the initial XML, and I may switch to that in the future, I'm not sure yet. Most examples online were using ObservableCollections of VeiwModel objects, so it was easier to learn this way.
<Window x:Class="WpfApplication3.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>
<DataTemplate x:Key="BlankTemplate" DataType="{x:Type ViewModel:MainViewModelBase}">
<View:BlankControl/>
</DataTemplate>
<DataTemplate x:Key="ParagraphTemplate" DataType="{x:Type ViewModel:MainViewModelBase}">
<View:ParagraphControl/>
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModel:MainViewModelBase}">
<ContentControl Content="{Binding}">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource BlankTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Type}" Value="Paragraph">
<Setter Property="ContentTemplate" Value="{StaticResource ParagraphTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
</Window.Resources>
<DockPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width=".5*"/>
<ColumnDefinition Width=".5*"/>
</Grid.ColumnDefinitions>
<TreeView Name="navigationPane" Grid.Column="0"/>
<ContentControl Grid.Column="1" Content="{Binding ElementName=navigationPane, Path=SelectedItem}"/>
</Grid>
After reading your post if I'm not wrong, you basically have a treeview on left hand side and on the basis of selection you want to populate some user control on right hand side. That's the crux of the requirement.
If above is the case what can be done is to bind the left and right side views to separate viewmodels. On selecting something from left hand side, you can pass the selection in the parameterized constructor of the right hand side view. Hence you'll have the input selected on LHS and can do processing on RHS
After some more Googling, I found a great project on codeproject that describes how to use the view model idea to make a load on command treeview. What I really liked about this was that it showed me how to use the INotifyPropertyChanged stuff to bind the isSelected property to something in the view model that could be used todo the XML loading etc.
I know this may seem basic to you WPF gurus out there, but this was so helpful in explaining to me how to really use the ViewModel pattern.
http://www.codeproject.com/Articles/26288/Simplifying-the-WPF-TreeView-by-Using-the-ViewMode
I am new to WPF/XAML so please bear with the noob question.
I have designed a control panel that will eventually function as a backend for my website, and have just finished laying out all the buttons in tabs using TabControl element. (this is designed using the Visual Studio 'Window' forms.
My question is, is it possible to create a function in the xaml.cs file that will dynamically handle a specific event for all my button elements ? for example...
I have 30+ buttons and dont want 30 different Click="btnCustomers_click" + their respective functions in the c# code. What I desire is say one function that would allow me to click any button and then open a new window depending on which button was selected.
The below code is my current design however for 30+ buttons their will be alot of functions and it will be messy, hence my desire to have one function control which window is opened depending on which button is clicked.
private void btnMerchants_click(object sender, RoutedEventArgs e)
{
var newWindow = new frmMerchants();
newWindow.Show();
}
Thanks in advance for any advice given!! :)
You could use a style for this:
<Style TargetType="{x:Type Button}">
<EventSetter Event="Click" Handler="btnMerchants_click"/>
</Style>
If you set this up in the resources somewhere without an x:Key it will apply to all buttons.
e.g. if you have a Grid and you want a certain style to apply to all Buttons in it you would define it like this:
<Grid>
<Grid.Resources>
<Style TargetType="{x:Type Button}">
<EventSetter Event="Click" Handler="Button_Click"/>
</Style>
</Grid.Resources>
<Grid.Children>
<!-- Buttons and stuff -->
</Grid.Children>
</Grid>
If you just want to apply it to some buttons set the x:Key and reference the style:
<Grid>
<Grid.Resources>
<Style x:Key="AttachClickHandlerStyle" TargetType="{x:Type Button}">
<EventSetter Event="Click" Handler="Button_Click"/>
</Style>
</Grid.Resources>
<Grid.Children>
<Button Content="Click me!" Style="{StaticResource AttachClickHandlerStyle}"/>
<Button Content="Click me, too!" Style="{StaticResource AttachClickHandlerStyle}"/>
<Button Content="Something different." Click="SomeOtherButton_Click"/>
</Grid.Children>
</Grid>
In general you should refactor any attributes that occur more than once into a style to prevent duplicate code.
Also, since you are a beginner the following articles might be of interest:
Styling and Templating
Resources Overview
You can use routed events on the parent container.
Example:
<Grid Button.Click="GeneralHandler">
<!-- Some stuff -->
</Grid>
In the code behind:
public void GeneralHandler(object sender, RoutedEventArgs e)
{
Button b = e.OriginalSource as Button;
//<-- Do something
}
You can read more about it on MSDN.
Just assign the exact same Click="btnCustomers_click" handler (with a general function name) to all the buttons. Then in the function open the correct window based on the sender's Name.