I am currently in the process of catching up on the theoretical basics of WPF and have come across a problem customizing grids.
My data source fills a grid with a number of properties that require different controls for editing. (say: combobox, date picker, tetbox etc.)
So far I have found 2 ways of accomplishing this:
listen to some event in the code behind and programmatically creating these controls in each grid cell.
Adding all edit controls I could need in each cell and binding their visibility to some selector variable that disables all the controls that aren't needed.
Both of these ways seem very untidy and hard to maintain. Is there a way to accomplish this in XAML only without having 6 or 7 active bindings to invisible and useless controls? What would be the proper way of doing this?
You can use an ItemsControl and use Templates to achieve this.
This is one of my recent controls. Basically I had several Options that were determined at runtime and each needed a visual representation on the UI. So I added an ItemsControl that - based on the bound property - selected the right ItemTemplate to display.
<ItemsControl ItemsSource="{Binding Path=DeliveryOrderOptions}" HorizontalContentAlignment="Stretch">
<ItemsControl.Resources>
<DataTemplate x:Key="DateTimeDataTemplate" DataType="{x:Type services:DeliveryOrderOption}">
<Grid Visibility="{Binding ConditionValid, Converter={StaticResource BooleanToVisibilityConverter}}">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="{Binding Path=Name}" />
<DatePicker Grid.Column="1"
SelectedDate="{Binding Path=Value, ConverterCulture=de-DE, ValidatesOnDataErrors=True, ValidatesOnExceptions=True, NotifyOnValidationError=True, UpdateSourceTrigger=PropertyChanged}"
ToolTip="{Binding Path=Error}" />
</Grid>
</DataTemplate>
<!-- more templates -->
</ItemsControl.Resources>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type services:DeliveryOrderOption}">
<GroupBox Header="{Binding Path=Title}" HorizontalContentAlignment="Center" Padding="6">
<ItemsControl ItemsSource="{Binding}" ItemTemplateSelector="{Binding Source={StaticResource OptionDataTemplateSelector}}" />
</GroupBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
It utilizes an DataTemplateSelector like this to find the right DataTemplate to apply to the item based on its property Type and if it has a Dictionary of acceptable values.
public class OptionDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
FrameworkElement element = container as FrameworkElement;
DeliveryOrderOption option = item as DeliveryOrderOption;
DataTemplate template = null;
if (element != null && option != null)
{
switch (Nullable.GetUnderlyingType(option.Type)?.Name ?? option.Type.Name)
{
case "String":
if (option.HasDictionary)
{
template = element.FindResource("StringComboBoxDataTemplate") as DataTemplate;
}
else
{
template = element.FindResource("DefaultDataTemplate") as DataTemplate;
}
break;
case "Int32":
if (option.HasDictionary)
{
template = element.FindResource("IntComboBoxDataTemplate") as DataTemplate;
}
else
{
template = element.FindResource("IntDataTemplate") as DataTemplate;
}
break;
case "DateTime":
template = element.FindResource("DateTimeDataTemplate") as DataTemplate;
break;
//... more options
default:
template = null;
break;
}
}
return template;
}
}
If the controls you wish to display at any given time have some sort of obvious logical grouping you could try creating a custom control for each group. This would make your xaml neater as your main page would only need to contain your custom controls (you might also be able to re-use them elsewhere).
Unfortunately I believe that you would still need to either place all controls on and then collapse them as required or add/remove them dynamically but you would only need to bind the visibility of your custom controls once rather than for each child element, you would also not have to see all the xaml they contain cluttering up your page.
Related
I have a UserControl that contains a TabControl that has an ItemTemplate that in turn has a Button. I want the user be able to change the content of that button, e.g. change it from TextBlock to Image.
A solution I thought of was to set the button's content from the Resources of the UserControl and overwrite the Resource by setting it on the ResourceDictionary of the entailing Window. Of course that does not work as StaticResource always resolves to the "closest" instance it can find.
I then thought of modifying the resource in the constructor of my UserControl, depending on some property. But it seems, one cannot change a resource. Below is a close sample showing the idea with a simple ListBox in a Window in which I try to change "What" to "How".
How would you approach this?
<Window.Resources>
<TextBlock x:Key="key" Text="What: " x:Shared="false" />
</Window.Resources>
<Grid Margin="10">
<ListBox Name="lbTodoList" HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<StaticResource ResourceKey="key" />
<TextBlock Text="{Binding}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
TextBlock tb = FindResource("key") as TextBlock;
tb.Text = "How: ";
List<string> items = new List<string>();
items.Add("Item 1");
items.Add("Item 2");
items.Add("Item 3");
lbTodoList.ItemsSource = items;
}
}
Instead of trying to override a resource, you should just treat it like any other XAML data-binding templating issue.
Instead of:
<StaticResource ResourceKye="key" />
Do something like this:
<TextBlock Text="{Binding LabelText}" />
In your UserControl set the default value of LabelText to "What:" and then allow the user to override the value. The data binding will take care of the rest.
If you want to have more dynamic content, then use a ContentControl instead and have properties for Content, ContentTemplate, and even ContentTemplateSelector depending on what you need to do.
<ContentControl
Content="{Binding MyContent}"
ContentTemplate="{Binding MyContentTemplate}"
ContentTemplateSelector="{Binding MyContentTemplateSelector}" />
This opens up a lot of flexibility.
I have an application in WPF that use the MVVM pattern and I use to have a list or observableCollection for example to binding comboBox, DataGrids... etc. However I have a control and I would like to add dynamically items of this control to the wrap pannel.
is a good idea in my view model create the view and view model of this control and add it to the list that is binding to the wrap pannel?
If this is not a good idea, how can I add items to the wrap pannel dynamically?
Thanks.
EDIT
I add code.
I am trying to do it but I don't get the solution.
I have a principal view model that create the view and the view model of my secondary view. In this principal view model I use this code:
ucDialogView myDialogView = new ucDialogView ();
ucDialogViewModel myDialogViewModel = new ucDialogViewModel ();
ucDialogView.DataContext = ucDialogViewModel;
The view model of my dialog is the following:
public class DialogViewModel : BaseViewModel
{
private ObservableCollection<ControlViewModel> _controls = new ObservableCollection<ControlViewModel>();
public ObservableCollection<ControlViewModel> Controls
{
get { return _myControls; }
set
{
_myControls = value;
base.RaisePropertyChangedEvent("Controls");
}
}
public myControlViewModel()
{
ControlViewModel myControlViewModel = new ControlViewModel();
Controls.Add(myControlViewModel);
}
}
My axml of my dialog:
<UserControl x:Class="MyApp.Views.DialogView"
Name="Principal"
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:my="clr-namespace:myApp.ViewModels"
mc:Ignorable="d"
d:DesignHeight="1122" d:DesignWidth="794" Width="794" Height="1122">
<ItemsControl Grid.Row="1"
ItemsSource="{Binding Controls, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel HorizontalAlignment="Center" Margin="0,15,0,0" Name="wrpControls" VerticalAlignment="Stretch" Width="Auto" Grid.Row="1" IsItemsHost="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type my:ControlViewModel}">
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</UserControl>
However, I get an empty dialog. Control is a user control, a view and its view model.
I think a better approach might be to define a DataTemplate for each ViewModel type you want to use. Then, you can just add the ViewModel objects themselves to the WrapPanel's items.
The WPF infrastructure will see there is a DataTemplate for this type of object and will take care of showing the associated DataTemplate.
You need to declare the DataTemplate in some scope that is accessible to your WrapPanel, for example in the Resources of a parent page/window or in App.xaml's Resources:
<DataTemplate DataType="{x:Type local:YourViewModelClass}">
<ComboBox>
<!-- Define your bindings and stuff here -->
</ComboBox>
</DataTemplate>
Note that WrapPanel doesn't have an ItemsSource property, so if you want to bind its items to a list of objects (like your ViewModels), you'll need to use an ItemsControl:
<ItemsControl ItemsSource="{Binding YourListOfViewModels}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
Assuming YourListOfViewModels is an ObservableCollection of your ViewModels, each time you add a ViewModel to that list, it will be displayed with the correct DataTemplate.
I have created a listbox with a tiled data template. What I am trying to figure out now is how to properly apply a scale effect to each listbox item when a mouse over or selected event occurs and have it render properly within the wrap panel. I currently have the animations added to the visual states of the ListBoxItemTemplate.
A couple of thoughts:
When the animation is called the tiles within the wrap panel do not reposition to allow for the scaled item to be viewed properly. I would like to have the items within the wrap panel re-position to allow for the item scaled to be visible.
Also I notice that the items when scaled are going beyond the boundary of the wrap panel is there a way to also keep the item when scaled constrained to the viewable area of the wrap panel?
Code used in in search view
<Grid x:Name="LayoutRoot">
<ListBox x:Name="ResultListBox"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="{x:Null}"
BorderThickness="0"
HorizontalContentAlignment="Stretch"
ItemContainerStyle="{StaticResource TileListBoxItemStyle}"
ItemsPanel="{StaticResource ResultsItemsControlPanelTemplate}"
ItemsSource="{Binding SearchResults[0].Results}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemTemplate>
<DataTemplate>
<formatter:TypeTemplateSelector Content="{Binding}" HorizontalContentAlignment="Stretch" Margin="2.5">
<!-- Person Template -->
<formatter:TypeTemplateSelector.PersonTemplate>
<DataTemplate>
<qr:ucTilePerson />
</DataTemplate>
</formatter:TypeTemplateSelector.PersonTemplate>
<!-- Incident Template -->
<formatter:TypeTemplateSelector.IncidentTemplate>
<DataTemplate>
<qr:ucTileIncident />
</DataTemplate>
</formatter:TypeTemplateSelector.IncidentTemplate>
</formatter:TypeTemplateSelector>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
ResultsItemsControlPanelTemplate is defined in app.xaml as
<ItemsPanelTemplate x:Key="ResultsItemsControlPanelTemplate">
<toolkit:WrapPanel x:Name="wrapTile"/>
</ItemsPanelTemplate>
I would greatly appreciate any suggestions on where to look
Thanks in advance
Image of current result
Render transformers are applied after a layout has occured, its purely graphical task that the Silverlight layout engine knows very little about. What you need is control that when scaled actually increases the size it desires and causes Silverlight to update its layout.
The control you need is the LayoutTransformer control from the Silverlight Toolkit.
Place the content of each of your tiles inside a LayoutTransformer and assign a ScaleTransform to its LayoutTransform property. Now you can get your animations to manipulate the transform and as the tile grows the other tiles will flow.
Working through this I have my user controls stored within within a Listbox datatemplate which is also a userControl referenced within the project. I think I have a partial solution started so now I need to just continue to tweak what is happening. Since I am using a datatemplate I had to set a binding reference on the user control to the Layouttrasnformation
<ListBox x:Name="ResultListBox"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="{x:Null}"
BorderThickness="0"
HorizontalContentAlignment="Stretch"
ItemsPanel="{StaticResource ResultsItemsControlPanelTemplate}"
ItemContainerStyle="{StaticResource TileListBoxItemStyle}"
ItemsSource="{Binding SearchResults[0].Results}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemTemplate>
<DataTemplate>
<formatter:TypeTemplateSelector Content="{Binding}" HorizontalContentAlignment="Stretch" Margin="2.5">
<!-- Person Template -->
<formatter:TypeTemplateSelector.PersonTemplate>
<DataTemplate >
<layoutToolkit:LayoutTransformer x:Name="PersonTransformer">
<layoutToolkit:LayoutTransformer.LayoutTransform>
<ScaleTransform x:Name="personScale"/>
</layoutToolkit:LayoutTransformer.LayoutTransform>
<qr:ucTilePerson MouseEnter="ucTilePerson_MouseEnter" Tag="{Binding ElementName=PersonTransformer}" />
</layoutToolkit:LayoutTransformer>
</DataTemplate>
</formatter:TypeTemplateSelector.PersonTemplate>
//rest edited for brevity
Then within the code behind of the usercontrol which holds the listbox I used the following:
private void ucTilePerson_MouseEnter(object sender, MouseEventArgs e)
{
var ps = ((UserControl)sender).Tag as LayoutTransformer;
if (ps != null)
{
var transform = ps.LayoutTransform as ScaleTransform;
transform.ScaleX = (transform.ScaleX + 1.2);
transform.ScaleY = (transform.ScaleY + 1.2);
Dispatcher.BeginInvoke(() => { ps.ApplyLayoutTransform(); });
}
I still have to tweak this some as it does not seem to be as fluid as I like ( and I have to also setup mouseLeave events).
Not sure if this is the most appropriate approach and I would appreciate any feedback on alternatives.
Thanks again to Anthony for pointing me in the right direction
I have a TreeView whose contents (nested TreeViewItems) are generated from a dataset via databinding, which all seems to work fine. The issue I'm running into is that when I try and manipulate the contents of the TreeViewItem headers in code, the Header property returns the DataRowView that the TreeViewItem was generated from and not, as I was expecting, the control generated by the template.
Here's an example of the template I'm using to generate the TreeViewItems:
<DataTemplate x:Key="seasonTreeViewItemTemplate">
<TreeViewItem>
<TreeViewItem.Header>
<CheckBox Content="{Binding Path=Row.SeasonID}" Tag="{Binding}" ToolTip="{Binding Path=Row.Title}" IsEnabled="{StaticResource seasonPermitted}" Checked="CheckBox_Checked" Unchecked="CheckBox_Unchecked" />
</TreeViewItem.Header>
<TreeViewItem Header="Championships" ItemTemplate="{StaticResource championshipTreeViewItemTemplate}">
<TreeViewItem.ItemsSource>
<Binding Path="Row" ConverterParameter="FK_Championship_Season">
<Binding.Converter>
<local:RowChildrenConverter />
</Binding.Converter>
</Binding>
</TreeViewItem.ItemsSource>
</TreeViewItem>
</TreeViewItem>
</DataTemplate>
Can anyone point out where I'm going wrong and advise me how to access the header checkboxes (ideally without delving into the VisualTree if possible)?
Thanks,
James
Well, after some searching I have found an adequate solution to the problem.
Using the following code, you can find named items in the template:
if (treeViewItem != null)
{
//Get the header content presenter.
ContentPresenter header = treeViewItem.Template.FindName("PART_Header", treeViewItem) as ContentPresenter;
if (header != null)
{
//Find a CheckBox called "checkBoxName"
CheckBox cb = treeViewItem.HeaderTemplate.FindName("checkBoxName", header) as CheckBox;
}
}
Also, for the benefit of anyone else who may not be too clued up on databinding treeviews: The template I posted in my question is not the right way to go about binding a treeview. Use a HierarchicalDataTemplate for each level of the tree. The direct content of the HierarchicalDataTemplate will specify the header content of each subtree and setting the ItemsSource and ItemTemplate properties will allow you to bind and format the subtrees children, for example:
<HierarchicalDataTemplate x:Key="templateName" ItemsSource="{Binding Path=someCollection}" ItemTemplate="{StaticResource someOtherTemplate}">
<TextBlock Text="{Binding Path=SomeProperty}" />
</HierarchicalDataTemplate>
I hope someone else will find this information useful.
I have a WPF ListView control containing a number of RadioButton controls using data binding. I'd like the first RadioButton in the group to be checked by default, preferably set in the XAML rather than programmatically, but haven't managed to achieve this.
My XAML for the control is:
<ListView ItemsSource="{Binding OptionsSortedByKey}" >
<ListView.ItemTemplate>
<DataTemplate DataType="{x:Type Logging:FilterOptionsRadioListViewModel}">
<RadioButton Content="{Binding Value}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The OptionsSortedByKey property is a SortedList.
I have done this clumsily by setting the IsChecked property of the RadioButton control in the Loaded event:
var button = sender as RadioButton;
if (button != null)
{
if (button.Content.ToString().ToUpperInvariant() == "ALL")
{
button.IsChecked = true;
}
}
I'd far prefer to do it via data binding in the XAML. Is there a simple way?
You could:
<RadioButton IsChecked="{Binding RelativeSource={PreviousData}, Converter={StaticResource NullAsTrueConverter}}"/>
Where the converter returns true if the value is null.
That said, an MVVM-based approach would make this a snap. You'd just:
<RadioButton IsChecked="{Binding IsChecked}"/>
And then your primary view model would set IsChecked to true for the first child view model in the collection.