How to expose properties of a WPF DataTemplate? - wpf

I have a data template that I use in many pages, the data template contains a few buttons, I want to hide some of these buttons by triggers (I mean setting the IsEnabled Property of these buttons in the page where I use this DataTemplate).
In other words, I would even like to set in style triggers/setters a property 'ButtonXIsEnabled', 'ButtonYIsEnabled' as part of the DataTemplate settable from the ListBox where I use this DataTemplate.
I really hope I am clear enough, please leave comments for any further details.
Any discussion will be really appreciated!
Thanks in advance.

Basically this depends on what object your using for your datatemplate. Instead of using some ButtonYIsEnabled, etcs. Try to use some words that fit better in to your domain model.
For example say you have a list of customers, and some of those customers have the ability to purchase discounted products. Then add a property to your Customer called CanPurchaseDiscountedProducts, and use that property in your DataTemplate
<DataTemplate TargetType="{x:Type local:Customer}">
<!-- Other Items -->
<Button Content="Purchase Discounted Products" x:Name="discounts" Visibility="Hidden" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding CanPurchaseDiscountedProducts}" Value="True">
<Setter TargetName="discounts" Property="Visibility" Value="Visible"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>

A WPF data template is a view of a certain object type... how you want an instance of ObjectTypeX to look. The data template can bind to properties on the underlying instance.
So if you have a ButtonXIsEnabled property on your instance, you can bind the corresponding Button's Visibility property to the instance property. The button would be shown or hidden based on the value in the underlying object.

Related

WPF - Set child controls of a user control to be readonly using binding

I have a WPF application with many different controls. I need to be able to set all child controls to be read only based on a property in my view model that I want to bind to.
There are a couple of challenges that I see:
How to ensure that setting the parent control to read only, also sets the child controls
Not all controls have a ReadOnly property - some IsReadOnly, some only have an IsEnabled
Has anyone any views on a generic solution rather than me having to bind the appropriate property (ReadOnly, IsReadOnly, etc) for each individual control?
Is there some way that I could use an attached property? Is there anyway, for example, that I could set a property on a grid, then in the code iterate through each child control setting it's appropriate property (if applicable at all)?
Any ideas welcomed.
David
I would recommend to do this using WPF implicit styles. The style would contain the Binding to the view model, for example:
<Style TargetType="{x:Type Button}">
<Setter Property="IsEnabled" Value="{Binding IsNotProcessing}" />
</Style>
As this style does not have the x:Key attribute set and uses the x:Type markup extension on the TargetType attribute, it is implicitly applied to all buttons in this case.
You would have to write an implicit style for each distinct control in your view as the following style would not be applied to all your buttons, text boxes and whatever controls you use (although the IsEnabled property is defined on FrameworkElement):
<!-- This implicit style is not applied as the x:Type must be the same type as
the targeted control; inheritance does not work here. -->
<Style TargetType="{x:Type FrameworkElement}">
<Setter Property="IsEnabled" Value="{Binding IsNotProcessing}" />
</Style>
Another option would be to make a single style that has a resource key an then reference this from every control, which is also quite cumbersome, but could be done relatively easy using Blend if you know all the controls at design time (you would select all controls and then apply the style using the properties window).
Hope this will help you.
Use the property IsHitTestVisible in xaml file to make real read only
<Grid IsHitTestVisible = "False">
//put a control
</Grid>

How to set SelectedItem of a ComboBox when item is not part of the ItemsSource collection?

Following scenario:
<ComboBox ItemsSource="{Binding Path=Names}"
SelectedItem="{Binding Path=Name, UpdateSourceTrigger=PropertyChanged}">
</ComboBox>
Names is a string collection which might change through code in the ViewModel. For example Names could be set to either male names (John, Paul, George) or female names (Mary, Jane, Karen).
Let's assume the male collection is active and the user selects "John" making it the SelectedItem. Now the collection changes to female names. By default the SelectedItem will be set to Null and John will not be displayed anymore. Instead an empty string will be displayed.
What I would like to achieve is that "John" is still visible as SelectedItem although it is not in the collection anymore. However as it is not part of the collection I would like to change the color of the SelectedItem to red.
I have played around with the IsEditable property, but didn't like the changing appearance of the ComboBox. The user should not be able to enter text manually anyway.
I tried to use different templates and styles (e.g. Template, ItemTemplate, ItemContainerStyle), but didn't find a way to use it to my favour.
Is it possible to style the provided ComboBox the way I need it or do I have to create my own user control?
EDIT:
I have found a solution I can live with.
<ComboBox IsEditable="True" IsReadOnly="True"
ItemsSource="{Binding Path=Names}"
Text="{Binding Path=Name}">
<ComboBox.Style>
<Style TargetType="{x:Type ComboBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsNameInCollection}" Value="False">
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="Foreground" Value="Black"/>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
In the ViewModel I make sure that the Name property does not change when the Names collection changes. The boolean property IsNameInCollection will be updated when the Name property or the collection changes.
Note: this no a real, working answer / solution, but just some thoughts you might find worth to consider:
First, the SelectedItem will always change to null if the collection changes (either by replacing it with a new collection or clearing and refilling it). So as you are binding to Name property of some object, can you prevent that property from being overridden with null there internally? That way the value might not change in the ComboBox (maybe you also need TwoWayBinding for Name to make the value stick; or even firing a change event, but that depends on some testing).
Second, the rendering needs to change, so the easiest way would be to use the ItemTemplate and a custom IValueConverter implementation to convert the information of the special entry with different color / style. But the downside of this is, that the ItemTemplate will normally be used for every entry, including the current selected.
One way to deal with this is to use an ItemTemplateSelector like described here: http://www.wpftutorial.net/datatemplates.html (section "How to use a DataTemplateSelector to switch the Template depending on the data")
Another way would be to use a container instead of plain string. That container would also hold a boolean value to indicte a different rendering. That boolean value could be used in the ItemTemplate. But in this scenario you need to wrap all string values.
Third, how should the ComboBox react if the selected value is still the old value that is not in the current list, and then the user selects another value. One logic would be that the old value just disappears, but then the user cannot select it back. If it should be still available in the list, then you must somehow add it to the list of current values, but mark it that it is a special value. In both cases you also need a logic that detects later if a value was chosen that is not valid according to the current available list.
I hope this helps to find a working solution.

Proper way to display a view using MVVM

I am brand new to both wpf and MVVM. I have a Mainwindow that has two views left side has a usercontrol with a listbox and the list box has a edit button inside of it. On the right I have another view that contains all my controls for viewing and editing the record. I can select an item in the list box and edit my record since using binding it automatically populates by the selectedItem object. What I want to do is when the user hits the edit button show the view on the right if they hit another button contained in the list box then show that view on the right and close the previous view. I think I am missing a big concept here since most of the examples are to simplistic and just show one view. I really dont want to have to do it in the code behind. I have looked at John smiths tab and would like to do something similur without the tabs though. Any help would be greatly appreciated.
It sounds like both views need to share the same context (ie ViewModel) then they will stay in synch automaticall by the magic of dependency properties...
I would probably try setting it up so that clicking either button (View or Edit) sets the DataContext of the right frame. The RightFrame View gets displayed using DataTemplates based on the DataContext.
So your xaml would be something like this:
<DataTemplate DataType={x:Type MyEditingViewModel}>
<!-- Editing Object View -->
</DataTemplate>
<DataTemplate DataType={x:Type MyViewingViewModel}>
<!-- Viewing Object View -->
</DataTemplate>
and your button click events would be something like this:
private void EditButton_Click(object sender, EventArgs e)
{
RightFrame.DataContext = new MyEditingViewModel((sender as Button).DataContext)
}
private void ViewButton_Click(object sender, EventArgs e)
{
RightFrame.DataContext = new MyViewingViewModel((sender as Button).DataContext)
}
So basically, what you're trying to do is have your view model decide which view it should be presented in, and make that decision in response to user choice.
How I do this sort of thing:
My view model exposes a view style property, which is an enumerable. In the view for the view model (usually a user control), I implement a DockPanel to contain each style of view. Each DockPanel is assigned a style, and the styles are defined like this:
<Style x:Key="Style_View1" TargetType="DockPanel">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ViewStyle}" Value="View1">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="Style_View2" TargetType="DockPanel">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ViewStyle}" Value="View2">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
(Obviously you don't need to use a DockPanel if a Grid or StackPanel is more appropriate for your scenario. And you can implement a different user control for each style of view if you want to keep your code nicely segmented.)
So as long as the value of ViewStyle is one that there's a corresponding style for, that view style will be visible. Since all of the styles set Visibility to Collapsed by default, there will only ever be at most one view style visible.
There are lots of ways of selecting the view style - create a command for each one and bind it to buttons, create a group of radio buttons and use a value converter, set the view style in response to other properties in the view model - whatever works.

WPF Dynamic GUI elements

In WinForms it was relatively easy to swap out Panels at runtime for other panels. In WPF this seems to be rather more complex (especially from XAML).
Can anyone provide clear guidance on the 'best practice' way of swapping gui elements at runtime (think pages in a wizard type situation).
Many thanks.
This can be approached in XAML using datatemplates and/or triggers. For example, if each page in your wizard were represented in an underlying model as a separate class or object, you could use one of the following two options... Both use a ContentControl, which is the perfect control for when the content will vary greatly between different views of the same data.
Please note that the bindings are intended as pseudocode examples, just to convey intent!
DataTemplate-based, using different classes for each page:
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type WizardPageOne}">
<!-- page 1 layout here -->
</DataTemplate>
<DataTemplate DataType="{x:Type WizardPageTwo}">
<!-- page 2 layout here -->
</DataTemplate>
<!-- ... etc -->
</Grid.Resources>
<ContentControl Content="{Binding CurrentPageModel, Source=Wizardmodel}" />
</Grid>
Or Trigger based, using a property that indicates the current page:
<ContentControl Content="{Binding WizardModel}">
<ContentControl.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding CurrentPageIndex} Value="1">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<!-- page 1 layout here -->
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding CurrentPageIndex} Value="2">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<!-- page 2 layout here -->
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<!-- .... etc -->
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
Both options will only load the control for each page as it's required, so you don't have all of the controls "loaded but hidden" in the window.
The underlying concepts of WinFomrs and WPF is different. In WPF it is not advisable to play around with UIElements(Controls) directly. Make use of DataBinding/DataContexts and just operate on the data and then the UI will function accordingly. This concept is all about WPF MVVM pattern. You can look in to some MVVM samples and try it before doing more complex WPF projects.
A simple example, Suppose you need to dynamically disply a number of items in a ListBox, The typical winform way to do this is to create Items and add directly to the ListBox. But in WPF you create an ObservableCollection<Customer> and bind that to the ListBox.ItemsSource. then define a DataTemplate for Customer Data Type, this ensure the WPF system to understand how a Collection of Customers being displayed in the application. So when you add a new customer instance to the collection, magically your ListBox will get updated with one more item. Seems pretty straight forward and a very loosely coupled way of Data and View right?.
Best wishes on your WPF learning. -
http://www.bing.com/search?q=WPF+MVVM
So the high level clue to your question is, make the View appropriately for the Data and when Data/Property Change happens, WPF will take care of changing the Panels/Controls. So it is really simple than WinForms way when you approach from the Data and View perceptive.
A couple options come to mind. If you create your components as UserControls, and make use of Data Binding, then you should be able to do what you need with minimal fuss.
Option one is to load each component into your parent container (grid, canvas, whatever) with Visibility="Collapsed", and then show and hide them as needed. This has the advantage that you can do this declaratively in XAML.
The other option is to load the components as you need them, so in the event handler of a button, or some other UI element. In this case you would probably want to remove the current displaying item from the Children collection of your host component, and then instantiate your next control, set the DataContext (this is why binding is important), and add it to the Children collection.
(disclaimer: this is based on my experience doing basically what you are asking in Silverlight 3.0, so there may be some WPF quirks I am unaware of).
The MVVM suggestions here are all good. But if you're designing a page-oriented UI that needs to be navigable, you can use Structured Navigation, too.
I got no idea if this is considered good practice, but what we did on one of our project is quite simple. We defined panels that were all on top of each other and would simply set the visibility to either hidden or visible when it was needed.

Giving a WPF ItemsControl a different look based off whether ItemsSource holds a single value or multiple values?

Are there any slick ways to style/template a WPF ItemsControl differently based off whether ItemsSource holds a single value or multiple values?
What I've done so far is to create a custom ItemsControl class which among other things displays the list of bound items as a horizontally oriented comma separated list. So far I'm pretty happy with the results however I want to show a more brief view of the bound data in cases where multiple values are bound and if only a single value is bound then I want to show a more extended view of the bound data with a longer string description. I figure this is probably best solved by dynamically choosing the template either based off a trigger or possibly by using a template selector but it's not yet clear to me how this would be done.
You could use a DataTrigger in your style to replace the template:
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=Items.Count}" Value="1">
<Setter Property="Template">
<Setter.Value>
<!-- Insert Template here -->
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
You could also add one for where the Value is 0 if you wanted to display a "no records" template.
You should use a StyleSelector.
Here is a sample.

Resources