I am writing a terminal in WPF, which communicates the host with an embedded device through RS232.
I want to be able to open multiple connections that will reside on different tabs, I believe that for that purpose WPF's tabContorl is sufficient, However the customer wants to be able to tile the different tabs on the screen, and as I understand the basic tabControl doesn't allow you to do that.
Any ideas how can this be done?
any help will be appreciated,
Thanks in advance.
Maybe it's overkill, but I would give a try to Avalon Dock from the WPF Toolkit, it's free. With that, you will be able to move terminals around, dock them wherever you wish and even have only opened at a time if you unpin others.
I have made a custom WPF control called ModalContentPresenter that allows you to display modal content which I think will be suitable. The control allows you to conditionally display additional content on top of your primary content.
Your application window will comprise a single ModalContentPresenter which will contain your TabControl in it's primary content and an ItemsControl in it's modal content.
<c:ModalContentPresenter IsModal="{Binding IsTiling}">
<DockPanel>
<Button Content="Show tiled view"
Command={Binding ShowTiledViewCommand}
DockPanel.Dock="Top"/>
<TabControl ItemsSource="{Binding Connections}"> />
</DockPanel>
<c:ModalContentPresenter.ModalContent>
<DockPanel>
<Button Content="Hide tiled view"
Command={Binding HideTiledViewCommand}
DockPanel.Dock="Top"/>
<Itemscontrol ItemsSource="{Binding Connections}" />
</DockPanel>
</c:ModalContentPresenter.ModalContent>
</c:ModalContentPresenter>
By default the modal content will not be displayed so all the user will see is the TabControl.
Both the TabControl and ItemsControl are bound to the same collection of data in your viewModel ensuring that the tiled view is in sync with the items in the tab control.
Your viewModel must have a Boolean property called IsTiling which should return true when you want the 'tiled' view to be shown and false when it is hidden.
When the tiled view is displayed users will be unable to interact with the primary content.
You can change the layout panel used by the ItemsControl to change how the collection of data is 'tiled'.
See this answer for an additional example of how to use the control.
The interface of your viewModel will look something like this:
public interface ViewModel {
public ObservableCollection<Connection> Connections { get; }
public boolean IsTiling { get; }
public ICommand ShowTiledViewCommand { get; }
public ICommand HideTiledViewCommand { get; }
}
Once you have this working you can add some additional enhancements to improve the look and feel of the user interface.
I would start by assigning a custom control template to the TabControl so that a Button is displayed in the 'header' area. This Button is bound to a command in your viewModel which is responsible for changing the IsTiling property to true.
This question provides a link to an answer which explores ways of achieving this.
A second enhancement is to remove the button from the modal content and call the HideTiledViewCommand command when the user selects an item in the items control. You can then add some additional logic which selects the correct tab when the tiled view is closed. I think this can be achieved by having an additional property in your viewModel representing the selected connection.
Related
How to build form at runtime in WPF-MVVM (PRISM) based application.
Requirement is like user should be able to add control like textbox, checkbox, combobox etc. at runtime.
after adding the crontol user will save the form and all the configuration will get saved in database.
So that application can create the form at runtime based on the configuration stored in database.
How can we achieve this?
Thanks.
I've done something similar, although not with standard UI controls. I have a series of classes representing the "controls" I want to display - in my scenario these represent physical devices like pumps, valves, switches, displayed on a machinery "control panel" that the user can configure. These classes inherit from a base class (call it "HardwareItem") which exposes some properties that are common to all controls, e.g. Top, Left, Width, Height, Tooltip, etc.
The "designer"
The window where the user "designs" a form consists of the following components:-
A "toolbox", basically an ItemsControl bound to a VM List<HardwareItem> property that exposes the available HardwareItems (created and populated by the VM's constructor)
A canvas, that the user can drag items onto from the toolbox. When a drop happens, I instantiate the appropriate HardwareItem object and add it to a collection (used to keep track of what controls have been added). To render the control on the canvas, I create a ContentControl and set its "Source" property to the HardwareItem object, then add that to the canvas at the drop position. The control's visual is rendered using XAML DataTemplates that I've created for each HardwareItem type.
A PropertyGrid control (part of the free Xceed toolkit). When the user selects a control on the canvas, the corresponding HardwareItem object is wired up to the PropertyGrid, allowing the user set its property values (I use a custom attribute to control which properties should appear in the grid).
When the user clicks "save", I basically just serialize my collection of HardwareItem objects to a string using Json.Net then saved to file.
"Runtime"
To render a previously designed form, the file is deserialized back into a collection of HardwareItem objects, and added to a canvas in pretty much the same way as described above.
Doing something similar with standard WPF controls shouldn't be too dissimilar. You could create classes that expose just the properties that you want a user to manipulate, e.g.:-
// Base class
public class MyControl
{
public double Top {get;set;}
public double Left {get;set;}
public double Width {get;set;}
public double Height {get;set;}
}
// TextBox
public class MyTextBox : MyControl
{
public string Text {get;set;}
}
// Button
public class MyButton : MyControl
{
public string Caption {get;set;}
public ICommand ClickCommand {get;set;}
}
The DataTemplates might look something like this:-
<DataTemplate DataType="{x:Type MyTextBox}">
<TextBox Text="{Binding Text}"
Width={Binding Width}" />
</DataTemplate>
<DataTemplate DataType="{x:Type MyButton}">
<TextBox Content="{Binding Caption}"
Width={Binding Width}"
Height={Binding Height}"
Command={Binding ClickCommand} />
</DataTemplate>
Much of the canvas manipulation is done in code-behind rather than the VM. It's "UI logic" so is a perfectly acceptable approach.
Prism is not used at all here (I use it for navigating between views, but it doesn't play a part in this "form designer" functionality).
I'm trying to create a WPF page project, in which I've split the screen in two. On the left side, I have four clickable links / buttons.
Clicking on one of the links opens a corresponding page on the right side of the screen. There, options can be set. When a user uses the navigation bar on top of the screen, it should apply on the right side only.
Is this possible to do? What would be a better approach?
I'd like to know how to tell a page (part) which page to load. So that would make the right page dynamic?
Would it be better to split up the Grid? Or would a DockPanel be a better solution?
I've created a large Window in WPF, but I'd like to split up all these pages in a usefull navigation Page. So I have a bit of experience in using WPF and XAML. How should I approach this problem?
Firt you need to identify the components you want, mainly I see three components, the LeftSide navigation pane, the TopSide navigation pane and the MainContent pane. First lets talk about the MainContent pane, I think the best way for doing this is to use binding and date templates for making this dynamically. In your ViewModel, or DataContext, you need to have a property that represent the Content that you want to show in the MainContent, lets call it MainContent, then the MainContent View could be a ContentControl and set the property Content bindings to the ViewModel's MainContent property. In this way you only need to set the DataTemplate for each class item that you want to show. Other way could be to use a tab control and chage the ControlTemplate, this way is not dynamic because you need predefine all contents that you will show.
Now, for the navigation pane, you could use any control, for instance you could use a Radio button and change the ControlTemplate, and make the logic in the view model, using commands, for instance.
And now, the use of the Grid or DockPanel depends of what you want your application to do. If you want a dynamic width, you should to use a Grid with a GridSplitter, but if you want fixed width, you could to use a DockPanel due it is a bit more efficient/faster than the Grid.
Hope this answers helps, and not to be confused. Please feedback if any doubt.
EDIT
For instance, in the view model:
public class MainViewModel:INotifyPropertyChanged
{
public object Content {get; set;} //Here you must to raise the PropertyChanged event
private ICommand _showSummaryCommand
public ICommand ShowSummaryCommand
{
get { return new _showSummaryCommand ?? (_showSummaryCommand = new SomeCommandImplementation((sender,e)=>{Content = new SummaryViewModel()},true))} //most of commands implementations recive two parameters, an delegate to execute and a bool delegate for can excecute
}
}
and for the view, in some resource dictionary:
<DataTemplate TargetType="{x:Type ViewModels:SummaryViewModel}">
<DataGrid>
<!--Here goes what ever you want to show for instace-->
<TextBlock x:Name="descriptionText" Text={Binding Description}/>
</DataGrid>
</DataTemplate>
and in the place where you will to show all the contents
<!--....-->
<ContentControl Content={Binding Content}/>
<!--....-->
Hope this code helps a bit more :)
I am defining a strategy where a main view will use data templates to switch between the views. Currently it can switch between 3 Views:
ApplicationView: it is actually the view that consists of lots of
different views, mostly layered out using tabs / docking. this is a
view that deals with application data.
LogInView: it is used for logging the user in.
DialogView: it is used to display dialog views. This view will also use data templates to select a proper view that is required.
The idea is that when a dialog view needs to be displayed, it is set as current view on the main view. After the selection is done, this information is passed to ApplicationView, or a view that is part of ApplicationView. While DialogView is shown, ApplicationView, must not be released from memory, since it ApplicationViewModel will still be manipulating with data (it needs to constantly work in the background).
I am thinking of achieving this using DataTemplates, and binding ContentControl's Content to CurrentView:
// in MainView
DataTemplate DataType="{x:Type vm:ApplicationViewModel}">
<vw:ApplicationView />
</DataTemplate>
.....
// in MainViewModel
public ViewModelBase CurrentView { get; set; }
Basically I am trying to avoid using modal windows for dialogs.
1) Is this strategy OK, or there are some problems that I am not aware with it?
2) When I switch to DialogView (I am actually switching viewmodels), what happens with the ApplicationView/ApplicationViewModel? Do I need to store ApplicationViewModel's reference somewhere, so it doesn't get garbage collected? I haven't tested this, but probably when I set CurrentView a new instance of ViewModel/View will be created.
3) Connected to second question, when using DataTemplates, what happens to View/ ViewModel that was previously used, and is now replaced with different view/viewmodel?
I don't see anything wrong with the way you're switching views, although typically you don't want to get rid of the application when you're displaying a dialog.
What I've done in the past is to put both the CurrentView, and the DialogView in a Grid so they are positioned on top of each other, then have the ApplicationViewModel contain an IDialogViewModel and IsDialogVisible properties, and when you want to display the dialog simply populate those two fields. (see below for an example)
You will have to store the ApplicationViewModel somewhere if you want to go back to it and avoid creating a new ApplicationViewModel
WPF disposes of UI objects that are no longer visible, so switching the CurrentView from Login to Application will get rid of the LoginView and create an ApplicationView
The ContentControl's Content is getting set to your ViewModel, so the ViewModel is actually being put in the applications VisualTree. Whenever WPF encounters an object in its VisualTree that it doesn't know how to draw, it will draw it using a TextBlock containing the .ToString() of the the object. By defining a DataTemplate, you are telling WPF how to draw the object instead of using its default .ToString() method. Once the object leaves the VisualTree, any visual objects that were created to render the object will get destroyed.
Although I would keep using what you currently have for switching Views, I would not use that method for the Login, Application, and Dialog views.
Typically the LoginView should only be displayed once when logging in, although it might get displayed again in a Dialog if you allow users to switch logins once logging in. Because of this, I typically show the LoginView in the startup code, then display the ApplicationView once login is successful.
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var login = new LoginDialog();
var loginVm = new LoginViewModel();
login.DataContext = loginVm;
login.ShowDialog();
if (!login.DialogResult.GetValueOrDefault())
{
Environment.Exit(0);
}
// Providing we have a successful login, startup application
var app = new ApplicationView();
var context = new ApplicationViewModel(loginVm.CurrentUser);
app.DataContext = context;
app.Show();
}
Like I said earlier, I wouldn't want to hide the Application when I display a Dialog, so I would make the Dialog part of the Application
Here's an example of the DataTemplate I would use for my ApplicationViewModel, using my own custom Popup from my blog for the Dialog
<Grid x:Name="ApplicationView">
<ContentControl Content="{Binding CurrentView}" />
<local:PopupPanel x:Name="DialogPopup"
Content="{Binding DialogContent}"
local:PopupPanel.IsPopupVisible="{Binding IsDialogVisible}"
local:PopupPanel.PopupParent="{Binding ElementName=ApplicationView}" />
</Grid>
Personally, I would find it easier to use ZOrdering within a standard grid, and put everything in the same view - using the ViewModel to manage visibility.
E.g
<Grid>
<Grid Visibility="{Binding IsView1Visible,
Converter={StaticResource BoolToVisibilityConverter}}">
<!-- view 1 contents -->
</Grid>
<Grid Visibility="{Binding IsView2Visible,
Converter={StaticResource BoolToVisibilityConverter}}">
<!-- view 2 contents -->
</Grid>
<Grid Visibility="{Binding IsView3Visible,
Converter={StaticResource BoolToVisibilityConverter}}">
<!-- view 3 contents -->
</Grid>
<Grid Visibility="{Binding IsDialogVisible,
Converter={StaticResource BoolToVisibilityConverter}}">
<!-- dialog contents contents -->
</Grid>
</Grid>
I have the scenario where I have some User controls Let say they are :
CreateStudents
CreateTeachers
Each of the User control have their own View model. The datacontext is set in initialization.
I have a main UI where these above User control can be loaded. So setting the datacontext works fine.
Problem statement
I have another User control "CreateClass" which is a collection of tabs. From here I can go to the above two user cotrols (Hidden tabs) .
The datacontext is set by two properties in CreateClassViewModel "CreateStudentManager" and "CreateTeacherManager".
This works fine but the issue is when i default open CreateClass UI, the other User controls also load (I guess because they have default constructor).
Because when I open CreateClass I donot want other controls to be loaded. These should be only loaded when they are called from the Create Class UI explictly.
How to achieve this ?
below is sample for one "CreateStudent"
<TabItem Header="Students" Visibility="{Binding IsStudentVisible, Converter={StaticResource BooleanToVisibilityConverter}}" >
<Grid>
<local:UCCreateStudent DataContext="{Binding CreateStudentManager}"/>
</Grid>
</TabItem>
Girija
Easiest would be to do it in code, ie. add the local:UCCreateStudent item to the grid on the desired event trigger. Give the grid a name (e.g. x:Name="MyGrid"), then
void OnTrigger(...)
{
UCCreateStudent NewStudent = new UCCreateStudent();
// extra code for setting the datacontext and any other layout properties
MyGrid.Children.Add(NewStudent);
}
There is a good article on codeproject:
http://www.codeproject.com/Articles/217022/Delaying-Element-Initialization-for-Collapsed-Cont
I'm using the Cinch MVVM framework, however I think this is something that relates to all WPF approaches.
I want to have a primary screen - Shell or MainWindow - which then contains various viewmodels. To navigate between viewmodels I'm using (or going to use) a tab control styled as a button strip with the content area beneath - which is all ok as I add the viewmodels to the tabcontrol (well to the 'Views' collection which is bound to the tab control) at runtime.
A screen that doesn't fit into this methodology is the sign in screen, which I don't really want to add to the tab control - preferably it should be in it's own usercontrol which takes up the entire screen apart from covering the logo; that is, I would like it to appear in the same window rather than a popup dialog, however I'm not sure how to add/ remove controls dynamically and then add subsequent tabcontrol once the user has signed in successfully (and remove the sign in screen). What containers should be used?
TIA.
The easiest way is put your tabcontrol in a Grid without columns and rows so it fill the grid by default. Then add an extra grid or loginusercontrol to the grid as shown below. The moment a user needs to login you can set the visibility of the MainTabControl" to collapsed and of the LoginGrid to Visible and switch it back after a succesfull login. I hope that the xaml below will help you enough.
<Grid>
<TabControl x:Name="MainTabControl" Visiblity="Visible">
... put your tabs here ...
</TabControl>
<Grid x:Name="LoginGrid" Background="#60FFFFFF" Visibility="Collapsed">
... put your usercontrol to login here or the login controls themself
</Grid>
</Grid>
You could use a ContentControl with content bound to a view model. So you'd have two view-models representing the sign-in screen and the main screen and use DataTemplate to display appropriate screen.
<Window.Resources>
...
<DataTemplate DataType="{x:Type my_view_models:SignInViewModel}">
<my_controls:SignInScreenView />
</DataTemplate>
...
</Window.Resources>
<ContentControl Content={Binding} />
You may be interested by Lakana, it is a lightweight and non intrusive navigation framework for WPF.
Riana