I have a WPF application made for an LCD display with a specific resolution. The WPF application is fixed size. The view is bound to its view model by DataTemplates like this:
<DataTemplate DataType="{x:Type vm:IdleViewModel}">
<v:IdleView/>
</DataTemplate>
Now I would like to make this application available for a second LCD display type. The view will be totally different. Is there any way to bind the view model to a different view depending the resolution?
There is more than one way to do this depending on your requirements. Let me focus on two different approaches that might work for you. How to find out the screen resolution is another topic, see:
How to get the size of the current screen in WPF?
Data Template Selector
You can create a data template selector that returns a DataTemplate based on screen resolution. The mechanism to determine the screen resolution is take from the question above.
public class ResolutionDependentTemplateSelector : DataTemplateSelector
{
public DataTemplate InvalidResolutionTemplate { get; set; }
public DataTemplate Resolution1Template { get; set; }
public DataTemplate Resolution2Template { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (IsTargetResolution(480, 576))
return Resolution1Template;
if (IsTargetResolution(720, 480))
return Resolution2Template;
return InvalidResolutionTemplate;
}
private bool IsTargetResolution(double width, double height)
{
return Math.Abs(SystemParameters.PrimaryScreenWidth - width) < 1 &&
Math.Abs(SystemParameters.PrimaryScreenHeight - height) < 1;
}
}
You can assign this selector in XAML. Since I do not know which control you are using, this example uses a simple ContentControl. The property to assign it to may vary.
<ContentControl>
<ContentControl.Resources>
<DataTemplate x:Key="Resolution1Template">
<!-- ...your markup. -->
</DataTemplate>
<DataTemplate x:Key="Resolution2Template">
<!-- ...your markup. -->
</DataTemplate>
</ContentControl.Resources>
<ContentControl.ContentTemplateSelector>
<local:ResolutionDependentTemplateSelector Resolution1Template="{StaticResource Resolution1Template}"
Resolution2Template="{StaticResource Resolution2Template}"/>
</ContentControl.ContentTemplateSelector>
</ContentControl>
Resource Dictionaries
You could create separate resource dictionaries that contain the data template for each different screen along with other specific resources that only apply to a certain screen size. Then on startup (e.g. in the Apps OnStartup method), merge the resource dictionary that fits the screen resolution into the application resources (App.Resources).
Depending on the size and complexity of your application you could separate the controls for each distinct screen resolution into its own project, similar to the resource dictionary approach. If the application is always run on a specific LCD type and never on another, it would also be possible to create targets for each, so each "platform" contains only the resources it needs.
Is there any way to bind the view model to a different view depending the resolution?
Not using a single view model type and a corresponding DataTemplate alone.
You could either
Use two different view model types and views
Implement the view to adopt itself according to the view model (which should then know about the current screen resolution)
Use a DataTemplateSelector to select the appropriate view based on some logic other than just the type of the view model
Related
I'm building a UWP app using the Template10 Minimal template. I have a list of ViewModels that share a common base class. They are bound to a Pivot as follows:
ItemsSource="{Binding EnabledModels}"
I've setup multiple data templates to map each ViewModel concrete type to the View (UserControl) created for that particular ViewModel as follows:
<Pivot.Resources>
<DataTemplate x:Key="gettingStarted" x:DataType="vm:GettingStartedViewModel">
<v:GettingStartedPart DataContext="{Binding}"></v:GettingStartedPart>
</DataTemplate>
<DataTemplate x:Key="packageSelection" x:DataType="vm:PackageSelectionViewModel">
<v:PackageSelectionPart DataContext="{Binding}"></v:PackageSelectionPart>
</DataTemplate>
</Pivot.Resources>
I've not been able to determine how to get the View to actually display. Currently it will only display the type name of the ViewModel. I'm sure I've messed up the bindings somehow.
My ultimate goal is to present a Pivot with a series of data collection screens that all share a common base ViewModel, but each screen has it's own data needs. I'd like to keep the screens as separate UserControl views and dedicated ViewModels to make them easier to maintain independently.
I've looked for other patterns for multi-screen data capture in UWP that don't require separate pages but haven't had any luck.
Thanks for any guidance you can provide!
I was able to get the DataTemplateSelector to work so the Pivot would display a PivotItem containing the appropriate View (UserControl) for each ViewModel in my list of EnabledModels. Thank you to commenters Will and AVK Naidu.
Good resource for this situation available here
Let me state problem first. I would like to implement wrapper around Canvas (let me call it Page) which would implement selecting rectangle around its UIElements which are actually selected.
For this I implemented ISelect interface like so :
interface ISelect {
Point Center {get; set;} //Center of selecting rectangle
Size Dimensions {get; set;} //Dimensions of selecting rectangle
}
Every object that is put to Page implements ISelect interface.
Page has SelectedElements of type ObservableCollection which holds reference to all currently selected elements.
For every entry in SelectedElements i would like to draw rectangle around it.
I have few ideas how to do this :
Every UIElement can implement on its own this rectangle and show it when selected. This option would require for new objects to implement this every time. So I rather not use it.
In Page I could create rectangles in code-behind in add them to the Page. It isn't MVVM recommended priniciple.
In Page XAML create somehind like ItemsControl and bind it to SelectedElements with specific template. This option seems like the best one to me. Please help me in this direction. Should I somehow use ItemsControl?
Thank you.
I don't have time to dig a complete working solution, so this is mostly a collection of suggestions.
Each element should have view model
public abstract class Element: INotifyPropertyChanged
{
bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
OnPropertyChanged();
}
}
}
public class EllipseElement : Element {}
public class RectangleElement : Element {}
Then there are data templates to visualize elements (I can't give you converter code, but you can replace it with another, look here).
<DataTemplate DataType="{x:Type local:EllipseElement}">
<Border Visibility="{Binding IsSelected, Converter={local:FalseToHiddenConverter}}">
<Ellipse ... />
</Border>
</DataTemplate>
<DataTemplate DataType="{x:Type local:RectangleElement}">
<Border Visibility="{Binding IsSelected, Converter={local:FalseToHiddenConverter}}">
<Rectangle ... />
</Border>
</DataTemplate>
Then bind ObservableCollection of elements to canvas (which is tricky, see this answer, where ItemsControl is used to support binding).
Your selection routine has to hit-test elements and set/reset their IsSelected property, which will show border. See here regarding how to draw over-all selection rectangle.
Hi,
I have a requirement to show receipt preview as part of WPF page. Sample of receipt is attached.
Each line of text on the receipt can have different alignment(some center, some right or left) and color depending on configuration. Also, the number of lines can vary for each receipt type. I am wondering which controls to be used to effectively implement this. I can create labels dynamically in code behind depending on number of lines and align each one differently with different foreground color but just looking for an effective way if there is any. The width of receipt does NOT vary but length may. Font is same for all lines and all receipt types. Any ideas are really appreciated.
Thanks
It is normally better to avoid dynamically adding controls like labels or textblocks from your code behind. This type of code is difficult to read and almost impossible to test. Instead, you should use a view-model class (look up the MVVM pattern). Your view-model could have a property returning a list of ReceiptItem and then in your view (the XAML file) you make an ItemsControl and bind it to your list of ReceiptItems. Now you can create a template for the ReceiptItem class so that they show up a desired using Label, TextBlock, or whatever you decide is appropriate.
For example, in C# you would need two classes:
public class MyReceiptViewModel
{
public List<ReceiptItem> ReceiptItems { get; set; }
}
public class ReceiptItem
{
public string Content { get; set; }
public bool IsHighlighted { get; set; }
}
Your view might look like (this assumes that you have an instance of MyReceiptViewModel as your data context):
<ItemsControl ItemsSource="{Binding ReceiptItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock
Text="{Binding Content}"
Foreground="{Binding IsHighlighted, Converter={StaticResource MyColorFromBooleanConverter}}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
In my wpf application the main view has 5 tabs with 5 different usercontrols , since the user controls are not related to each other, I have created 5 different view models (apart from the main viewmodel).
I thought of having a List or dictionary to have the list of usercontrols and its viewmodels,
Now, I would like to bind the tabitems with the list of usercontrols and assign the datacontexts, but since the list or dictionary can be changed, I dont find a way to bind the usercontrols to the tabitems.
For example, If I have a single tab which will be associated with a usercontrol I can assign
tab1View tview=new tab1View();
tview.DataContext= new tab1ViewModel();
tab1.Content=tview;
But how can I do the same from a list which has the reference of the view and viewmodels of the usercontrols?
Please teach me a best way to achieve this.
**Answer: **
I got the answer for what I need.
First, Generic type collection of the view models should be created
C# - Multiple generic types in one list
public abstract class Metadata
{
}
public class Metadata<DataType> : MetaData where DataType : class
{
private DataType mDataType;
}
List<Metadata> metadataObjects;
metadataObjects.Add(new Metadata<tab1ViewModel>());
metadataObjects.Add(new Metadata<tab2ViewModel>());
Then create a DataTemplate selector if multiple views are to be be referenced with same viewmodel or just apply the DataTemplate
There are a few ways to handle this, though I'd look at using frameworks to help you with MVVM. I myself promote Prism.
View Injection
View Discovery
DataTemplates - Sample
With DataTemplates you're defining in XAML (or in code, but XAML is more likely) which view to "automagically" apply to a ContentControl based upon the view-model (DataContext).
Somewhere in the XAML resources:
<DataTemplate DataType="{x:Type ViewModel:GeneralSettingsViewModel}">
<View:GeneralSettingsView/>
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModel:AdvancedSettingsViewModel}">
<View:AdvancedSettingsView/>
</DataTemplate>
Somewhere in the XAML file that has the resources applied to it:
<TabControl ItemsSource="{Binding MyViewModelCollection}" />
Note: This only works if you have one view-model per DataTemplate in the scoped resource.
DataTemplateSelector
If you have a view-model that can be applied to multiple views and you determine those views through additional logic, you would want to use a DataTemplateSelector. Here is an example:
Somewhere in the XAML resources:
<!-- Possible collision because the DataType is of the same type -->
<DataTemplate x:Key="GeneralSettingsTemplate"
DataType="{x:Type ViewModel:SettingsViewModel}">
<View:GeneralSettingsView/>
</DataTemplate>
<DataTemplate x:Key="AdvancedSettingsTemplate"
DataType="{x:Type ViewModel:SettingsViewModel}">
<View:AdvancedSettingsView/>
</DataTemplate>
<local:SettingsDataTemplateSelector x:Key="SettingsTemplateSelector"
GeneralSettingsTemplate="{StaticResource GeneralSettingsTemplate}"
AdvancedSettingsTemplate="{StaticResource AdvancedSettingsTemplate}" />
Somewhere in the XAML file that has the resources applied to it:
<TabControl ItemsSource="{Binding MyViewModelCollection}"
ItemTemplateSelector="{StaticResource SettingsTemplateSelector}" />
SettingsTemplateSelector.cs:
public class SettingsDataTemplateSelector : DataTemplateSelector
{
public DataTemplate GeneralSettingsTemplate { get; set; }
public DataTemplate AdvancedSettingsTemplate { get; set; }
public override DataTemplate SelectTemplate(Object item,
DependencyObject container)
{
var vm = item as SettingsViewModel;
if (vm == null) return base.SelectTemplate(item, container);
if (vm.IsAdvanced)
{
return AdvancedSettingsTemplate;
}
return GeneralSettingsTemplate;
}
}
MSDN: Prism Navigation - http://msdn.microsoft.com/en-us/library/gg430861(v=PandP.40).aspx
This covers Prism Regions as well as other parts of navigation.
MSND: View Discovery vs View Injection - http://msdn.microsoft.com/en-us/library/ff921075(v=pandp.20).aspx
This section covers the differences of View Discovery and View Injection and when to use each.
Create a collection of your viewmodels that you bind to the ItemsSource of the tab control. Then create a DataTemplateSelector to select a view for each viewmodel.
I've created a simple WPF application which has two Windows. The user fills in some information on the first Window and then clicks Ok which will take them to the second Window. This is working fine but I'm trying to incorporate both Windows into a single Window so just the content changes.
I managed to find this Resource management when changing window content which seems like it is what I'm after. However, I've search for ContentPresenter but couldn't find much help for how I need to use it. For example, if I use a ContentPresenter, where do I put the existing XAML elements that are in the two Windows? I'm guessing the first Window will go into the ContentPresenter but the second one will need to be put somewhere for when it needs to be switched in.
Any help would be great. A simple working example would be even better.
TIA
A ContentPresenter is normally used when restyling existing controls. It is the place where the Content of a control is placed. Instead you should use a ContentControl, which is simply a control that has a content element. Alternatively, you could directly set the Content of your window.
You extract the contents of your two existing windows into two UserControls. Then you create a new Window which will host the contents. Depending on your business logic, you set the content of that window (or that window's ContentControl if you want additional "master" content) to either of those two UserControls.
EDIT:
As a starting point. This is not complete working code, just to get you started. Note that this is bad architecture; you should probably use a MVVM or similar approach once you get this running!
<Window>
<ContentControl Name="ContentHolder" />
</Window>
<UserControl x:Class="MyFirstUserControl" /> <!-- Originally the first window -->
<UserControl x:Class="MySecondUserControl" /> <!-- Originally the second window -->
In code behind of Window:
// Somewhere, ex. in constructor
this.ContentHolder.Content = new MyFirstUserControl;
// Somewhere else, ex. in reaction to user interaction
this.ContentHolder.Content = new MySecondUserControl;
I use ContentPresenter for snapping in content. In the window, I put something like this:
<ContentPresenter Content="{Binding MainContent}" />
In the view model, I have a property called MainContent of type object:
public object MainContent { get { return (object)GetValue(MainContentProperty); } set { SetValue(MainContentProperty, value); } }
public static readonly DependencyProperty MainContentProperty = DependencyProperty.Register("MainContent", typeof(object), typeof(SomeViewModel), new FrameworkPropertyMetadata(null));
Whatever you set MainContent to will show up in the window.
To keep the separation between view and view model, I typically set the MainContent property to another view model and use a data template to map that view model to a view:
<DataTemplate DataType="{x:Type viewmodels:PlanViewModel}">
<views:PlanView />
</DataTemplate>
I put that data template in some central resource dictionary along with a bunch of other view-model-to-view mappers.