Assign controls to a property in XAML - wpf

Hej all,
I wonder if it's possible to assign more than one control to a property in XAML.
Say I have 2 controls in my XAML:
<Button x:Name="_Btn1 Content="Button 1" />
<Button x:Name="_Btn2 Content="Button 2" />
<local:MyControl x:Name="_MyCtrl" Controls="{what goes here?}" />
Or should I declare my control as a container control and put all controls inside it, like so:
<local:MyControl x:Name="_MyCtrl">
<Button x:Name="_Btn1 Content="Button 1" />
<Button x:Name="_Btn2 Content="Button 2" />
</local:MyControl>
Thnx in advance!
Grtz,
Dwi

You can do that if you make MyControl inherit ItemsControl.
Then this
<local:MyControl x:Name="_MyCtrl">
<Button x:Name="_Btn1 Content="Button 1" />
<Button x:Name="_Btn2 Content="Button 2" />
</local:MyControl>
will work and this
<local:MyControl x:Name="_MyCtrl" ItemsSource="ViewModelorControlCollection" />
will work too.
The ViewModelorControlCollection can be defined in your ViewModel or as a static resource in your xaml.

It really depends what you're gonna do. Usually you'd simply define a DataTemplate instead.
<local:MyControl x:Name="_MyCtrl">
<local:MyControl.ControlsTemplate>
<DataTemplate>
<StackPanel>
<Button x:Name="btn1" Content="Button 1" />
<Button x:Name="btn2" Content="Button 2" />
</StackPanel>
</DataTemplate>
</local:MyControl.ControlsTemplate>
</local:MyControl>

You definitely need some kind of container. This can be a simple list or a specialized collection. Whatever you use in your control.
<local:MyControl>
<local:MyControl.Controls>
<ControlCollection> <!-- or whatever -->
<Button/>
<Button/>
</ControlCollection>
</local:MyControl.Controls>
</local:MyControl>
If you use a Panel or similar classes, you don't need to specify the ControlCollection explicitly. You then can define the controls like with e.g. the StackPanel.

Related

WPF how to set stackpanel as resources and reuse it in TabControls

I am new to C# and WPF so please give me some ideas:
I have an WPF app used to display some stack panels,all stack panels default Visibility is set to collapsed and they will switch to visible according to the received data.
Now I want to make all these stack panels to resources so I can reuse it in some new added tab controls and stack panels.
<StackPanel x:Name="ColorOption" Visibility="Collapsed">
<TextBlock Text="Line Color" Style="{StaticResource ItemNameTextBlockStyle}"/>
<Button Style="{StaticResource ColorButtonStyle}" Click="Color_Click">
<Button.Content>
<Rectangle x:Name="LineColorRect" Style="{StaticResource ColorSelectionRectangleStyle}" />
</Button.Content>
</Button>
</StackPanel>
Above is one example of stack panels I am using. In the code behind the function "Color_Click" will change this "ColorOption" stack panel state and do something.
However after I try to put this stack panel into Windows.Resources
<Window.Resources>
<StackPanel x:Name="ColorOption" Visibility="Collapsed" x:Key="ColorOption">
<TextBlock Text="Line Color" Style="{StaticResource ItemNameTextBlockStyle}"/>
<Button Style="{StaticResource ColorButtonStyle}" Click="Color_Click">
<Button.Content>
<Rectangle x:Name="LineColorRect" Style="{StaticResource ColorSelectionRectangleStyle}" />
</Button.Content>
</Button>
</StackPanel>
</Window.Resources> (I also put the style files inside)
In the tab controls I did
<TabControl>
<TabItem Header="Tab 1" Content="{StaticResource ColorOption}"/>
</TabControl>
The visual studio shows error in the code behind says "ColorOption does not exist in the current context"
How can I fix this? Is any way to set the context? thank you
You can simply wrap the StackPanel in ContentControl and make it ControlTemplate.
<ControlTemplate x:Key="ColorOptionTemplate" TargetType="{x:Type ContentControl}">
<StackPanel x:Name="ColorOption" Visibility="Collapsed">
<TextBlock Text="Line Color" Style="{StaticResource ItemNameTextBlockStyle}"/>
<Button Style="{StaticResource ColorButtonStyle}" Click="Color_Click">
<Button.Content>
<Rectangle x:Name="LineColorRect" Style="{StaticResource ColorSelectionRectangleStyle}"/>
</Button.Content>
</Button>
</StackPanel>
</ControlTemplate>
However, you will need to change properties of controls inside the ContentControl and it would be cumbersome. So the StackPanel could be wrapped in UserControl instead.
<UserControl x:Class="WpfApp1.ColorOptionControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel x:Name="ColorOption" Visibility="Collapsed">
<TextBlock Text="Line Color" Style="{StaticResource ItemNameTextBlockStyle}"/>
<Button Style="{StaticResource ColorButtonStyle}" Click="Color_Click">
<Button.Content>
<Rectangle x:Name="LineColorRect" Style="{StaticResource ColorSelectionRectangleStyle}"/>
</Button.Content>
</Button>
</StackPanel>
</UserControl>
This is a common way in WPF. The downside is that you will need to add dependency properties to UserControl and wire up them with dependency properties of internal controls so that you can set their values at the level of UserControl and bridge them with external controls and window. This could be complicated and cumbersome as well.
So I think ideally it would be better to find an existing control which has similar functionalities you want and create a custom control deriving from the existing one.

How to get my original ViewModel?

I created a User Control that has ViewModelA() as its ViewModel then inside my View, there's a StackPanel that uses ViewModelA.Data as DataContext.
My problem is inside this StackPanel, I have a button that needs to implement my created ICommand inside ViewModelA(). How can I do that?
Is there anything like <Button DataContext="DataContext.Parent" /> or something like that?
Here's how I implemented my ViewModel and View:
App.xaml
<DataTemplate DataType="{x:Type vm:ViewModelA}">
<vw:ViewA />
</DataTemplate>
ViewA.xaml (where the button inside the stack panel should implement the ICommand)
<StackPanel x:Name="RightPaneDetails"
Grid.Column="1"
Margin="15,0,0,30"
DataContext="{Binding Data}">
<!-- Some controls goes here that binds to ViewModelA.Data properties -->
<StackPanel Orientation="Horizontal">
<Button DataContext={Binding} Command="{Binding LookupCommand}" /> <!-- This button should implement the ViewModelA.LookupCommand -->
</StackPanel>
</StackPanel>
TIA
PS, ViewModelA.Data is my model.
It looks like you have set DataContext as ViewModelA in your UC. So, you can use
<Button DataContext={Binding} Command="{Binding DataContext.LookupCommand, RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}}" />
It can also be written as :
<Button DataContext={Binding DataContext, RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor} } Command="{Binding LookupCommand}" />
Although #AnjumSKhan's solution is correct, I would like to propose alternative solution:
<StackPanel x:Name="RightPaneDetails"> <!-- do not set bind datacontext here -->
<!-- Some controls goes here that binds to ViewModelA.Data properties, e.g: -->
<TextBlock Text="{Binding Data.SomeProperty}" />
<!-- If there too many elements bound to Data, you can group them in stackpanel -->
<StackPanel Orientation="Horizontal">
<!-- This button is databound to ViewModelA.LookupCommand
DataContext doesn't have to be set, since it's inherited from parent. Actually entire stackpanel is redundant here-->
<Button Command="{Binding LookupCommand}" />
</StackPanel>
</StackPanel>
As you can see I just avoided setting DataContext on the parent RightPaneDetails so the children can easily access both ViewModelA.Data's properties (TextBlock) and ViewModelA's properties (Button)
Few other notes:
Notice, that instead of
RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}
you can write just
RelativeSource={RelativeSource AncestorType=UserControl}
Alternative solution to RelativeSource is ElementName:
<StackPanel x:Name="Root">
<StackPanel DataContext="{Binding Data}">
<Button Command="{Binding DataContext.LookupCommand, ElementName=Root}" />
I preffer this because existence of "Root" element is checked at compile time and it is more readable.
Avoid binding DataContext and another Property on the same element, e.g:
<Button DataContext="{Binding....}" Command="{Binding ...}" />
There is no guarantee, which binding will be evaluated first. You can, however, set IsAsyc=True to the second binding Command={Binding ..., IsAsync=True} to ensure it will be evaluated later than non async bindings

How to arrange controls horizontally with even spacing in WPF

I am looking for a flexible way to arrange rectangular controls inside a grid with an even spacing. The controls are added/removed dynamically. Eventually I wish to let user select one of the layouts below:
I am thinking of using UniformGrid and let the rest be handled by grid's properties:
<UniformGrid>
<Button Content="Button 1"/>
<Button Content="Button 2"/>
<Button Content="Button 3"/>
<Button Content="Button 4"/>
<Button Content="Button 5"/>
</UniformGrid>
However, my concern is my future ability to customize the layout as in option 3 (see image above). I am looking for suggestions of how to make this as flexible as possible considering I am following MVVM in my design.
Use a UniformGrid and bind the count for Columns and Rows. Use it as the panel for an items control and you can get MVVM separation.
<ItemsControl ItemsSource={Binding X}>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows={Binding...} />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
A WrapPanel can be used to display any number of items, and will automatically wrap to another row if the items exceed the allowed width.
<WrapPanel>
<Button Content="Button 1"/>
<Button Content="Button 2"/>
<Button Content="Button 3"/>
<Button Content="Button 4"/>
<Button Content="Button 5"/>
</WrapPanel>
Alternatively if you're looking for something more structured where you can define a dynamic layout for controls, I'd suggest an ItemsControl with the ItemsPanelTemplate set to either a UniformGrid, WrapPanel, or Grid depending on your requirements. James Lucas' answer provides a good example for that.

Silverlight: Binding a Property to a DependencyProperty

The XAML code:
<Canvas>
<Button x:Name="btnCanvasButton" Content="Canvas Button"
Canvas.Left="50" />
<Button x:Name="btnCanvasButton2" Content="Canvas Button 2"
Canvas.Top="25"
Width="{Binding Path=Canvas.Left, ElementName=btnCanvasButton}" />
</Canvas>
I want to bind btnCanvasButton2.Width to btnCanvasButton.Canvas.Left, but it's not working.
I also tried Path=Canvas.LeftProperty, Path=Left, Path=LeftProperty, but no luck either.
Please advise. Thx.
Peter
You need to use parentheses to bind to an attached property.
You could try:
<Button x:Name="btnCanvasButton2" Content="Canvas Button 2"
Canvas.Top="25"
Width="{Binding Path=(Canvas.Left), ElementName=btnCanvasButton}" />

wpf layout-system problem

I'm not too familiar with wpf layout-system. so i'm ready to start and understanding that. at the first of road i have a problem with wpf. so according to below markup, i have 4 button which when we run project everything is true.
<Window ... WindowStartupLocation="CenterOwner" SizeToContent="WidthAndHeight" Name="wMessage" ShowInTaskbar="False" ResizeMode="NoResize" WindowStyle="SingleBorderWindow">
<WrapPanel Orientation="Horizontal">
<Button Content="Button 1" Margin="10" />
<Button Content="Button 2" Margin="10" />
<Button Content="Button 3" Margin="10" />
<Button Content="Button 4" Margin="10" />
</WrapPanel>
at the right side and bottom of window something appears like a border which i don't know this comes from where!!!
alt text http://www.4freeimagehost.com/uploads/098a981c7a36.png
The problem i that you have given a Margin="10" for every button. By default it takes that single value for all the Four sides. If you don't want the Blank Space for Top and bottom but want them between the buttons then cahnge the XAML to following.
<Button Margin="12,0" Content="Button1"/>
<Button Margin="12,0" Content="Button1"/>
<Button Margin="12,0" Content="Button1"/>
<Button Margin="12,0" Content="Button1"/>
By default if you give only 2 values, first one is taken for both Left and Right and second one is taken for both Top and Bottom.

Resources