Removing duplicated XAML code by using custom control? - wpf

What is a good way to get rid of repeating XAML in different files. Example :
<StackPanel Grid.Row="8" Grid.Column="2" Style="{StaticResource ViewContentStyle}" Visibility="{Binding Type, Converter={StaticResource TypeToVisibility}}">
<ctl:NewLabel LabelContent="{x:Static common:LocalResources.UNameLabel}" LabelStyle="{DynamicResource ContentLabelStyle}"
ImageStyle="{DynamicResource ViewContentControlStyle}">
<ctl:ETextBox x:Name="UserName" HorizontalAlignment="Left" Style="{StaticResource {x:Type TextBox}}"
LostFocus="Textbox_OnLostFocus"
Text="{Binding Path=UserName, UpdateSourceTrigger=PropertyChanged, NotifyOnValidationError=True, ValidatesOnDataErrors=True}">
</ctl:ETextBox>
</ctl:NewLabel>
</StackPanel>
<StackPanel Grid.Row="9" Grid.Column="2" Style="{StaticResource ViewContentStyle}" Visibility="{Binding SelectedAuthenticationType, Converter={StaticResource AuthToVisibility}}">
<StackPanel Orientation="Horizontal" KeyboardNavigation.TabNavigation="None">
<Label Style="{DynamicResource ContentLabelStyle}" Content="{x:Static common:LocalResources.UPasswordLabel}"/>
<AdornerDecorator>
<PwdBox x:Name="Password"
HorizontalAlignment="Left"
LostFocus="Textbox_OnLostFocus" PasswordChar="*"
</PwdBox>
</AdornerDecorator>
</StackPanel>
I have 3 files where almost the same code is reused. I think there is a way to get rid of this by using a common custom control. However, I dont see much examples as to how it can be done. Any leads would be great.

Add a new UserControl to your project and move the common XAML to this one.
You could then create an instance of this UserControl (replace "UserControl1" with the actual name of your UserControl) in any other view:
<!--insert the shared markup here: -->
<local:UserControl1 />

Related

MVVM : Binding Commands with Collection to Listbox in WPF

i have a list box in which there are different controls like button , text box etc in its item template, i have a collection which i bind with listbox, it works fine , but now i want to move my code to MVVM , and i write some commands in my View Model for clicks events of buttons , how can i bind my collection + my commands to list box ??? because commands are not in the collection, this is the Data Template for my list Box
<DataTemplate x:Key="listItemTemplate">
<Grid ShowGridLines="False">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<DockPanel Grid.Row="0" Name="commentsPanel" LastChildFill="False" MinWidth="350">
<TextBlock Name="txtUserName" IsEnabled="False" Text="{Binding UserName}"
Width="Auto" DockPanel.Dock="Left" Foreground="GhostWhite" Margin="0,6,0,0"></TextBlock>
<TextBlock Name="txtDate" IsEnabled="False" Text="{Binding CreateDt}"
Width="Auto" DockPanel.Dock="Left" Foreground="Green" Margin="4,6,0,0"></TextBlock>
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal" Width="{Binding EditPanelWidth}" x:Name="EditDeletePanel" Visibility="{Binding ButtonVisibilityText }">
<Button Name="btnEdit" Content="Edit" Width="Auto" DockPanel.Dock="Right" Height="20"
Click="btnEdit_Click_1" Margin="4,4,0,4" Foreground="GhostWhite" VerticalContentAlignment="Top" Visibility="{Binding ButtonVisibilityText}"></Button>
<Button Name="btnDelete" Content="Delete" Width="Auto" Height="20" VerticalContentAlignment="Top" DockPanel.Dock="Right" Visibility="{Binding ButtonVisibilityText}"
Click="btnDelete_Click_1" Margin="4"></Button>
</StackPanel>
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal" x:Name="SaveCancelPanel" Visibility="{Binding CancelSaveEnableText}">
<Button Name="btnSave" Content="Save" Width="Auto" Height="20" DockPanel.Dock="Right"
Click="btnSave_Click_1" Margin="4"></Button>
<Button Name="btnCancel" Content="Cancel" Height="20" Width="Auto" DockPanel.Dock="Right"
Click="btnCancel_Click_1" Margin="4"></Button>
</StackPanel>
</DockPanel>
<dxe:TextEdit ShowBorder="False" Grid.Row="1" Name="txtComment" Width="Auto" Foreground="Red"
TextWrapping="WrapWithOverflow" EditValue="{Binding Note}" IsEnabled="{Binding IsCommentTextEnable}">
</dxe:TextEdit>
<dxe:TextEdit Text=".............." Grid.Row="2" ShowBorder="False" IsEnabled="False">
</dxe:TextEdit>
</Grid>
</DataTemplate>
and here is the collection + my commands which i want to bind to buttons ,
public ICommand CancelCommand
{
get { return _cancelCommand ?? (_cancelCommand = new CommandHandler(Cancel)); }
set { _cancelCommand = value; }
}
public TList<ProgramNote> NotesCollection
{
get { return _notes; }
set
{
_notes = value;
RaisePropertyChanged("NotesCollection");
}
}
I know i can use this code to bind my commands with button
<Button Command={Binding CancelCommand}
but this command is not present in the collection , i am new in MVVM , kindly help , may be i am missing some little thing to bind my commands , but i am confused that how to add commands in my collection , so that i can get them in my view
You can bind the commands to your data template buttons etc by finding the appropriate viewmodel
example
<DataTemplate x:Key="listItemTemplate">
<Button Command="{Binding DataContext.CancelCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=LixtBox}}"
CommandParameter="{Binding}">
</DataTemplate>
in example we'll find the datacontext of LixtBox which I assume to be your viewmodel then will bind to the command and pass the current object as the command parameter to perform actions on.
you'll then receive the item as parameter in the implementation of your command
public void Execute(object parameter)
{
ProgramNote note = parameter as ProgramNote;
//your logic here, eg cancelling download etc.
}
Thanx to all of you specially thanx to #Sheridan and #PushPraj, I am able to do it now , here is the code of data template in which i have a button
<Button Name="btnCancel" Content="Cancel" Height="20" Width="Auto" DockPanel.Dock="Right"
Command="{Binding DataContext.CancelCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=dxe:ListBoxEdit}}"
CommandParameter="{Binding}" Margin="4"></Button>
and this is the code of ListBox
<dxe:ListBoxEdit Name="listComments" Grid.Row="1" ItemTemplate="{StaticResource listItemTemplate}"
ItemsSource="{Binding NotesCollection}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Visible" >
</dxe:ListBoxEdit>
and lastly this is my back end code
listComments.DataContext = viewModel;
It's always difficult to answer new user's questions because they always leave out important information from their questions. However, judging by your question text, it sounds to me like you have set your collection property as the DataContext of your Window. If you want to data bind to your commands instead, then you'll need to change the DataContext to the object that contains both the collection and the commands... an instance of your view model:
DataContext = new YouViewModel();
Now that the DataContext is set to an instance of your view model, you can data bind to its properties as you showed us:
<Button Command="{Binding CancelCommand}" />
...
<ListBox ItemsSource="{Binding NotesCollection}" />
Ahhh, sorry I misunderstood - so your Button is inside the ListBox. In that case, you could try something like this:
<Button Command="{Binding DataContext.CancelCommand,
RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
This should reach out of the scope of the collection, looking in the DataContext of a Window, so if you have set the instance of your view model to the Window.DataContext, this should work.

WPF UserControl or ControlTemplate... (not sure)

I have a listbox where I have to add about 20 static custom items. All the items are based on the same template (something like that) :
<Border>
<StackPanel Orientation="Horizontal">
<Image Source="" Height="30" />
<TextBlock Text="" VerticalAlignment="Center" />
</StackPanel>
</Border>
I don't want to repeat that 20 times in the ListBox.Items I would like to have some kind of UserControl where I could do something Like the following where I could set some custom properties :
<ListBox>
<ListBox.Items>
<MyListBoxTemplate x:Name="Item1" ItemText="Item #1" ItemImageSource="/Image1.jpg" />
<MyListBoxTemplate x:Name="Item2" ItemText="Item #2" ItemImageSource="/Image2.jpg" />
...
</ListBox.Items>
</ListBox>
But I don't wan't to create a userControl just for that!!! Is there an easy way to put that template in the Window.Resources?
Thanks
If you are ONLY using it for that SPECIFIC listbox, you can just assign the ItemTemplate property. This will need to work in conjunction with a collection of custom objects defined in your resources somewhere else. This will save you from creating a custom UserControl, but you will need an object that can be defined in XAML and a list of them in XAML anyway. To be honest, creating a UserControl is relatively painless and may be easier, but it is possible without doing so.
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate TargetType="CustomObjectType">
<Border>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding ImageSource}" Height="30" />
<TextBlock Text="{Binding TextContent}" VerticalAlignment="Center" />
</StackPanel>
</Border>
<DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
EDIT: If you are going to use it in more than one place, put the DataTemplate in your Application resources and ive it a key, then assign the ItemTemplate property to {StaticResource MyListBoxItemsTemplateKey}
Not my favorite approach since it uses the XmlDataProvider and XPath syntax (which I tend to always forget). But you can embed your static data as xml within your Window.Resources like so:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Window.Resources>
<XmlDataProvider x:Key="MyStaticData" XPath="StaticItems" >
<x:XData>
<StaticItems xmlns="">
<StaticItem>
<ItemText>Item #1</ItemText>
<ItemImageSource>/Image1.jpg</ItemImageSource>
</StaticItem>
<StaticItem>
<ItemText>Item #2</ItemText>
<ItemImageSource>/Image2.jpg</ItemImageSource>
</StaticItem>
</StaticItems>
</x:XData>
</XmlDataProvider>
</Window.Resources>
<Grid>
<ListBox>
<ListBox.ItemsSource>
<Binding Source="{StaticResource MyStaticData}" XPath="StaticItem" />
</ListBox.ItemsSource>
<ListBox.ItemTemplate>
<DataTemplate>
<Border>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding XPath=ItemImageSource}" Height="30" />
<TextBlock Text="{Binding XPath=ItemText}" VerticalAlignment="Center" />
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
Then within your ListBox bind to the XmlDataProvider you specified and use the XPath notation within the bindings to drill down to the data you want the controls to bind to.
This site has a couple good examples too:
http://vbcity.com/blogs/xtab/archive/2010/12/24/more-xpath-examples-in-a-wpf-application.aspx
Hope this helps!

How to create a standard DataTemplate for DataGridTemplateColumn?

I've got a DataTemplate for a DataGridTemplateColum wich looks like this:
<toolkit:DataGridTemplateColumn x:Name="DataGridTextColumnIstVorvorjahr" IsReadOnly="True" SortMemberPath="SummeIstVorvorjahr">
<toolkit:DataGridTemplateColumn.CellTemplate >
<DataTemplate>
<Grid HorizontalAlignment="Stretch" Background="Transparent" Margin="0,-5">
<DockPanel VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<TextBlock Panel.ZIndex="100" Style="{DynamicResource CellText}" Text="{Binding Path=SummeIstVorvorjahrGerundet, Converter={StaticResource numberFormatter}, ConverterParameter='#,0.0 T€'}" DockPanel.Dock="Right"/>
<Image Panel.ZIndex="90" DockPanel.Dock="Left" MouseLeftButtonUp="FilterDataGridAnalyse_MouseDoubleClick" HorizontalAlignment="Left" Margin="5,0,0,0" Width="20" Height="20" Visibility="Hidden" Name="ImageNormal" Source="pack://application:,,,/Cis.Common.Presentation;component/Resources/Images/Lupe.png" />
</DockPanel>
</Grid>
<DataTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="ImageNormal" Property="Visibility" Value="Visible" />
</Trigger>
</DataTemplate.Triggers>
</DataTemplate>
</toolkit:DataGridTemplateColumn.CellTemplate>
<toolkit:DataGridTemplateColumn.HeaderTemplate>
<DataTemplate >
<DockPanel VerticalAlignment="Stretch" HorizontalAlignment="Stretch" LastChildFill="False">
<TextBlock x:Name="TextBlockHeaderZeile1" Text="Ist" DockPanel.Dock="Top" />
<WrapPanel DockPanel.Dock="Top">
<TextBlock x:Name="TextBlockHeaderZeile2" Text=""/>
<ContentPresenter x:Name="contentPresenter">
<ContentPresenter.Content>
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Content" />
</ContentPresenter.Content>
</ContentPresenter>
</WrapPanel>
<Border Style="{DynamicResource borderline}">
<TextBlock VerticalAlignment="Stretch" x:Name="TextBlockSumme" Text="{Binding Path=KumulierteSummeIstVorvorjahr, Converter={StaticResource numberFormatter}, ConverterParameter='#,0.0 T€', RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type cis:ChildWindow}}}"
/>
</Border>
</DockPanel>
</DataTemplate>
</toolkit:DataGridTemplateColumn.HeaderTemplate>
</toolkit:DataGridTemplateColumn>
Now I want to make a StandartTemplate for this Type because I've got many Colums like this, with only differ in the bindings of the texts in the colums as well as in their headers.
As far I've tried to make a Style for this, but this won't work, I tried to create an usercontrol (but I think it's like taking a sledgehammer to crack a nut).
So any help or hint how to solve this problem would be appreciated.
I don't see why you've rejected the UserControl approach. UserControls are pretty lightweight. They add very little overhead at runtime. They are an extra feature in your project of course, but I usually find that to be an improvement - WPF projects with a small number of large Xaml files are typically hard to maintain.
Far from being a 'sledgehammer', they seem like exactly the right solution here to me.
Add the DataTemplate into the Resources and then access it via a StaticResource
<Window>
<Window.Resources>
<DataTemplate x:Key="MyColumnTemplate">
...
</DataTemplate>
<DataTemplate x:Key="MyColumnTemplateHeader">
...
</DataTemplate>
</Window.Resources>
...
<toolkit:DataGridTemplateColumn x:Name="DataGridTextColumnIstVorvorjahr" IsReadOnly="True" SortMemberPath="SummeIstVorvorjahr"
CellTemplate={StaticResource MyColumnTemplate}
HeaderTemplate={StaticResource MyColumnTemplateHeader}
...
</Window>
If I understand you, you try to bind the same column template with different data and to have different header's content relative with column data. So, you may use "dynamic XAML" (XAML used in C# code - that is dynamic) which allows you to use one template for different data.
Here a simple example.
In C# code we create DataGridTemplateColumn object:
DataGridTemplateColumn tc = new DataGridTemplateColumn();
Then we set the CellTemplate property with template which is created dynamically in special function:
tc.CellTemplate = (DataTemplate)XamlReader.Parse(GetTextCellDataTemplate(someText));
Here is a special function which creates our template:
public static string GetTextCellDataTemplate(string bindingPath)
{
return #"
<DataTemplate
xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"" >
<ScrollViewer MaxHeight=""200"" MaxWidth=""250"" VerticalScrollBarVisibility=""Auto"">
<TextBlock Text=""{Binding Path=" + bindingPath + #"}""
TextWrapping=""Wrap"" />
</ScrollViewer>
</DataTemplate>";
}
Now you may send various information in this function as text and get the same template. You may choose the template from the information you want to put in the cell. For this you must write various function which will return various templates.
The same approach can be applied to header template.

WPF - simple relative path - FindAncestor

In the XAML below the ToolTip correctly binds to RelativeSource Self. However, I can't for the life of me work out how to get the TextBlock in the commented block to refer to SelectedItem.Description
<Controls:RadComboBoxWithCommand x:Name="cmbPacking"
Grid.Row="2"
Grid.Column="5"
ItemsSource="{Binding PackingComboSource}"
DisplayMemberPath="DisplayMember"
SelectedValuePath="SelectedValue"
SelectedValue="{Binding ElementName=dataGrid1, Path=SelectedItem.PackingID}"
ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=SelectedItem.Description}"
IsSynchronizedWithCurrentItem="True"
Style="{StaticResource comboBox}">
<!-- <Controls:RadComboBoxWithCommand.ToolTip>-->
<!-- <TextBlock Text="{Binding RelativeSource={RelativeSource Self}, Path=SelectedItem.Description}" TextWrapping="Wrap" Width="50"/>-->
<!-- </Controls:RadComboBoxWithCommand.ToolTip>-->
</Controls:RadComboBoxWithCommand>
I would appreciate any suggestions
Thanks - Jeremy
It seems that since ToolTip does not have a parent, you need to bind to the placement target as below:
<Controls:RadComboBoxWithCommand Grid.Row="2"
Grid.Column="5"
ItemsSource="{Binding PackingComboSource}"
DisplayMemberPath="DisplayMember"
SelectedValuePath="SelectedValue"
SelectedValue="{Binding ElementName=dataGrid1, Path=SelectedItem.PackingID}"
IsSynchronizedWithCurrentItem="True"
Style="{StaticResource comboBox}">
<Controls:RadComboBoxWithCommand.ToolTip>
<ToolTip DataContext="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget}">
<TextBlock Text="{Binding SelectedItem.Description}"
TextWrapping="Wrap"
Width="100" />
</ToolTip>
</Controls:RadComboBoxWithCommand.ToolTip>
</Controls:RadComboBoxWithCommand>
Hope this is useful for someone
Jeremy
A relative source of self means the current object, which would be the TextBox itself in this particular case. You want a relative source of find ancestor with an ancestor type of RadComboBoxWithCommand. Alternatively, and perhaps a little simpler, is to give the combo box a name and use ElementName in your binding instead of a relative source:
<ComboBox x:Name="cb" ...>
<ComboBox.ToolTip>
<TextBlock Text="{Binding SelectedItem.Description, ElementName=cb}" .../>

How can I make an "Accordion Widget" in WPF?

The goal:
I'm trying to achieve something like this in WPF:
(source: wordpress.org)
An initial solution:
At the moment, I'm trying to use an ItemsControl with an ItemTemplate composed of an Expander.
I want a consistent look for the Header portion of the Expander, but I want the Content portion of the Expander to be completely flexible. So, it's basically a set of "portlets" stacked vertically, where each portlet has a consistent title bar but different content.
The code so far:
This is what I have at the moment:
<ItemsControl
Grid.Row="2"
Grid.Column="2">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Expander>
<Expander.HeaderTemplate>
<DataTemplate>
<StackPanel
Orientation="Horizontal">
<TextBlock
FontSize="14"
FontWeight="Bold"
Text="Title_Of_Expander_Goes_Here" />
<TextBlock
Margin="10,0,0,0"
FontWeight="Bold"
FontSize="18"
Text="*" />
</StackPanel>
</DataTemplate>
</Expander.HeaderTemplate>
<Expander.Template>
<ControlTemplate
TargetType="Expander">
<Border
BorderThickness="1">
<ContentPresenter />
</Border>
</ControlTemplate>
</Expander.Template>
</Expander>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.Items>
<StackPanel>
<TextBlock
FontSize="14"
FontWeight="Bold"
Text="Users:" />
<wt:DataGrid
Margin="0,1,0,0"
AutoGenerateColumns="False"
CanUserAddRows="True"
CanUserDeleteRows="True"
ItemsSource="{Binding Source={StaticResource Main_SystemUsers}, XPath=//Users/*}">
<wt:DataGrid.Columns>
<wt:DataGridTextColumn
Header="User Name"
Binding="{Binding XPath=#UserName}" />
<wt:DataGridComboBoxColumn
Header="Role"
ItemsSource="{Binding Source={StaticResource Main_UserRoles}, XPath=//Roles/*}"
SelectedValueBinding="{Binding XPath=#Role}" />
</wt:DataGrid.Columns>
</wt:DataGrid>
<StackPanel
Margin="0,10,0,0"
Orientation="Horizontal">
<Button
Content="Add New User..." />
<Button
Margin="10,0,0,0"
Content="Delete User..." />
</StackPanel>
</StackPanel>
</ItemsControl.Items>
</ItemsControl>
Discussion:
The only thing that shows up when I run this is the DataGrid of users and the buttons ("Add New User" and "Delete User") below it. There is no Expander or title bar. Also, even if I did see one, I'm not sure how to set up a Binding for the title that appears on the title bar. I know how to do bindings if I use ItemsSource, but I wanted to set my items declaratively.
The question:
How should I go about this? I'm looking for either a fix for what I have now or a clean-sheet solution.
Edit:
What I ended up doing was replacing the ItemsControl with a StackPanel and just writing a style for my expanders. This proved to be much simpler, and there really was no benefit to the ItemsControl since I needed to declare custom content for each item anyway. The one issue remaining was how to achieve a custom title for each expander. That's where #Thomas Levesque's suggestion to use TemplateBinding came in. All I had to do was replace Text="Title_Of_Expander_Goes_Here" in my header's template (see code above) with Text="{TemplateBinding Content}".
You're not seeing the Expander because you redefined its template. This one should work better :
...
<Expander.Template>
<ControlTemplate
TargetType="Expander">
<Border
BorderThickness="1">
<Expander Content="{TemplateBinding Content}" Header="{TemplateBinding Header}"/>
</Border>
</ControlTemplate>
</Expander.Template>
...
Personally I think a TreeView control would give you a much better base to work from, especially if you're using Expression Blend as a basis to create new/blank Templates from for items. Seeing the default Templates is extremely enlightening and gives you much more fine-grained control and better understanding and insight into how things work by default. Then you can go to town on them. It also looks like you're working with Hierchical Data and TreeViews inherently lend themselves well to working with such data.

Resources