I have a XAML main window that contains a header, a central area and a footer (in a grid). The central area contains a ContentControl which is set throw a binding (using MVVMLight). The header/footer is always the same so no problems there.
The part that goes into the ContentControl is always quite similar, they are WPF usercontrols and have a left part that contains info and a right part with at least an OK and BACK button.
These are viewmodels and their views:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<TextBlock Text="this changes and contains other controls too" />
</Grid>
<Grid Grid.Column="1">
<!-- more buttons and statuses-->
<Button Content="Back" Margin="5" Height="30" />
<Button Content="Ok" Margin="5" Height="30" />
</Grid>
</Grid>
Is there a way i could create a base class/custom control for those views? So that I could write something like this in my xaml:
<basewindow>
<leftpart>
custom XAML for this view
</leftpart>
<rightpart>
custom XAML for this view
</rightpart>
</basewindow>
I could then remove duplicate code that is now in each of those views to the base class while still keeping the ability to write my xaml in the editor. Or is this not feasible?
To clarify are you trying to inherit the visual element that exist in XAML, like you can do in WinForms? If so you cannot do this in WPF. There is no Visual inheritence in WPF.
Now if you aren't trying to inherit visual element it is easy. First create your UserControlBase class and add you event handler. Keep in mind this base class can not have any XAML associated with it. Code only
public class MyUserControlBase : UserControl
{
public MyUserControlBase()
{
}
protected virtual void Button_Click(object sender, RoutedEventArgs e)
{
}
}
Now create another UserControl that does have a xaml counter part. Now you will need to change the root elemtn in the XAML to your base class like this:
<local:MyUserControlBase x:Class="WpfApplication7.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication7">
<Grid>
<Button Click="Button_Click">My Button</Button>
</Grid>
</local:MyUserControlBase>
And don't forget the code behind:
public partial class MyUserControl : MyUserControlBase
{
public MyUserControl()
{
InitializeComponent();
}
}
Notice the button in the derived user control is calling the Button_Click event handler we defined in the base class. That is all you need to do.
Related
I have spend a little over a Day on this problem and i am absolutely Clueless.
If i click the button to show the Second View it Opens, but without Content.
I even get by a breakpoint in the View Model.
For this i have reduced everything to a Simple Textbox and Textblock that shut display the same Data, but they do not. They show nothing even after Typing into the Box the Block does not update.
But what ever i try the Databinding does not Work. Does anyone has an Idea?
Thanks in Advance
My second View
<Window x:Class="AoE4_BO_Overlay.Views.EditorView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:AoE4_BO_Overlay.Views" xmlns:viewmodels="clr-namespace:AoE4_BO_Overlay.ViewModels" d:DataContext="{d:DesignInstance Type=viewmodels:EditorViewModel}"
mc:Ignorable="d"
Title="EditorView" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="20" />
<ColumnDefinition Width="20" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="20" />
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="20" />
</Grid.RowDefinitions>
<TextBlock Text="{Binding Path=FirstName}" Grid.Column="0" Grid.Row="2"/>
<TextBox Text="{Binding Path=FirstName , Mode=OneWay}" Grid.Column="0" Grid.Row="1"/>
</Grid>
My ViewModel
internal class EditorViewModel : Conductor<object>
{
private string _firstName = "Tom";
public EditorViewModel()
{
}
public string FirstName
{
get
{
return _firstName;
}
set
{
_firstName = value;
NotifyOfPropertyChange(() => FirstName);
}
}
}
How i call both of them
public void CreateBO_Click(object sender, RoutedEventArgs e)
{
EditorView createBO = new EditorView();
ActivateItemAsync(new EditorViewModel());
createBO.Show();
}
added Information
public partial class EditorView : Window
{
public EditorView()
{
DataContext = new EditorViewModel();
InitializeComponent();
}
}
You have two separate issues:
1. Typing into the textbox doesn't change the bound property:
This is expected, since you use OneWay binding explicitly. OneWay binding means the property updates the user interface, but not the other way around. So changing FirstName should update the TextBox, but changing the TextBox doesn't update FirstName.
Interestingly enough, if you just omit the Mode = OneWay part, it should work - since TextBoxes should use TwoWay binding by default. I recommend you define your TextBox binding explicitly as Mode = TwoWay
2. Your view initializes with an empty TextBlock / TextBox
This one is harder to pin down, since you don't show us where you set your DataContext. This usually happens to me when I set the DataContext AFTER InitializeComponent(), instead of before. You either set the DataContext before the binding is initialized (as part of InizializeComponent()), or you have to raise a NotifyPropertyChanged on your property to update the UI afterwards.
If this is not the cause, you might want to enable WPF binding errors in your output console - that usually gives you a good idea of where your bindings fail. Visual Studio has an option for that. It should be located here:
Tools -> Options -> Debugging -> Output Window -> WPF Trace Settings
-> Data Binding -> All
I believe what you are attempting here is to show your second View (EditorView) within the first one (and not as a pop-up - if you intend to have it as popup, use WindowManager instead of ActivateItemAsync).
One thing you need to change for making this possible is to ensure your second View is a UserControl and not a Window.
// EditorView.xaml.cs
public partial class EditorView : UserControl
// EditView.xaml
<UserControl x:Class="AoE4_BO_Overlay.Views.EditorView"
Also since your using the ActivateItemAsync, you would need to ensure that your FirstView contains a ContendControl with Name "ActiveItem".
// FirstView.xaml
<ContentControl x:Name="ActiveItem"/>
The call to ActivateItemAsync would use this control to load the View of your second ViewModel (EditorViewModel). With this in place, you could now use the ActivateItemAsync method to load the View.
public async Task CreateBO_Click(object sender, RoutedEventArgs e)
{
await ActivateItemAsync(new EditorViewModel());
}
Please note that method ActivateItemAsync supports asynchronous calls and it would be wise to call the method asynchronously.
Another point to note is that you do not need to specify the DataContext explicitly as seen in the OP if you are using Caliburn Micro and the View/ViewModels are stored in the recommended folder/namespaces structures. Caliburn Micro uses naming conventions to associate the appropriate view-viewmodel pairs. More information on the same could be found in the official documentation
I have the following xaml view:
<UserControl x:Class="MyViews.PersonView"
xmlns:views="clr-namespace:MyViews"
[...]
>
[...]
<dxb:BarManager x:Name="MainBarManager">
<dxb:BarManager.Items>
<dxb:BarButtonItem x:Name="bbiPrint"
Content="{Binding Print, Source={StaticResource CommonResources}}"
Command="{Binding PrintPersonsCommand}"
CommandParameter="{Binding PersonsCardView, ElementName=CardUserControl}"
/>
</dxb:BarManager.Items>
<Grid>
<Grid.RowDefinitions>
[...]
</Grid.RowDefinitions>
<views:CardView x:Name="CardUserControl" Grid.Row="2"/>
</Grid>
[...]
</UserControl>
The CardView is defined as follows:
<UserControl x:Class="MyViews.CardView"
[...]>
[...]
<dxg:GridControl ItemsSource="{Binding Persons}" SelectedItems="{Binding SelectedPersons}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" SelectionMode="MultipleRow">
[...]
<dxg:GridControl.View>
<dxg:CardView x:Name="PersonsCardView"
[...]
CardTemplate="{StaticResource DisplayCardTemplate}"
PrintCardViewItemTemplate="{StaticResource PrintCardTemplate}"/>
</dxg:GridControl.View>
[...]
</dxg:GridControl>
</UserControl>
The PrintPersonsCommand is defined as follows in my ViewModel:
public class PersonViewModel
{
public PersonViewModel(...)
{
[...]
PrintPersonsCommand = new Prism.Commands.DelegateCommand<DataViewBase>(PrintPersons, CanPrintPersons);
}
public Prism.Commands.DelegateCommand<DataViewBase> PrintPersonsCommand { get; private set; }
private void PrintPersons(DataViewBase view)
{
_printService.ShowGridViewPrintPreview(view);
}
private bool CanPrintPersons(DataViewBase view)
{
return true;
}
}
Now, when I click the Print button, the above PrintPersons method is always fed with null. How do I pass CardUserControl.PersonsCardView in my MyViews.PersonView xaml above, how do I pass that PersonCardView to my command? In other words, how do I fix
CommandParameter="{Binding PersonsCardView, ElementName=CardUserControl}"
to make it work?
Currently, the only solution I've found to this problem is to replace the Command and CommandParameter with
ItemClick="OnPrintBtnClick"
and then in the PersonView's code-behind file to do:
private void OnPrintBtnClick(object sender, ItemClickEventArgs e)
{
var ctxt = DataContext as PersonViewModel;
ctxt.PrintPersonsCommand.Execute(CardUserControl.PersonsCardView);
}
That works but I can't believe there is no other way. I'm not happy with that solution because I don't have the benefits of using the Command any more, like e.g. the automatic evaluation of the Command's CanExecute method. I could also put the CardView's xaml code in the PersonView.xaml but I like my controls to be in separate files because I have the feeling it's more structured and each user control has its own responsibilities which can nicely be split into separate files. Also, that solution binds my view to my view model too tightly.
Can someone help me out please?
Without changing your existing view and viewmodel hierarchy, I was able to pass the GridControl.View to the PersonViewModel using the Tag property
You can assign the CardView to the Tag property at the bottom of your CardView UserControl, and then access this Tag as CommandParameter.
CardView UserControl
<UserControl x:Class="MyViews.CardView"
[...]>
[...]
<dxg:GridControl ItemsSource="{Binding Persons}" SelectedItems="{Binding SelectedPersons}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" SelectionMode="MultipleRow">
[...]
<dxg:GridControl.View>
<dxg:CardView x:Name="PersonsCardView"
[...]
CardTemplate="{StaticResource DisplayCardTemplate}"
PrintCardViewItemTemplate="{StaticResource PrintCardTemplate}"/>
</dxg:GridControl.View>
[...]
</dxg:GridControl>
<UserControl.Tag>
<Binding ElementName="PersonsCardView"/>
</UserControl.Tag>
</UserControl>
Print Button Xaml:
<dxb:BarButtonItem x:Name="bbiPrint"
Content="{Binding Print, Source={StaticResource CommonResources}}"
Command="{Binding PrintPersonsCommand}"
CommandParameter="{Binding ElementName=CardUserControl, Path=Tag}"
/>
Based on the valuable input of Insane, I came up with the following two cleaner fixes:
Code-behind solution
In the PersonView, use the ItemClick event handler on the Print button:
<dxb:BarButtonItem x:Name="bbiPrint"
Content="{Binding Print, Source={StaticResource CommonResources}}"
ItemClick="OnPrintBtnClick"/>
Adapt the corresponding code-behind file like this:
public partial class PersonView : UserControl
{
readonly IPrintService _printService;
public PersonView(IPrintService printService)
{
_printService = printService;
InitializeComponent();
}
private void OnPrintBtnClick(object sender, ItemClickEventArgs e)
{
_printService.ShowGridViewPrintPreview(CardUserControl.PersonsCardView);
}
}
Because I want to gray-out the Print button when there is no selection, I still need to add some code to make that happen. I can get it by
1. updating the button code to
<dxb:BarButtonItem x:Name="bbiPrint"
Content="{Binding Print, Source={StaticResource CommonResources}}"
ItemClick="OnPrintBtnClick" IsEnabled="{Binding CanPrintPersons}"/>
refreshing the CanPrintPersons property in the PersonViewModel upon Persons selection change
That's it.
CardViewModel solution
In that solution, we have a PersonView with its underlying PersonViewModel and a CardView with its underlying CardViewModel. I will not describe that solution with all the details as it is overkill in my situation but for the sake of completeness, I'll give the main points. Upon clicking the Print button on the PersonView, the PersonViewModel's PrintCommand is called. That command emits a Print event to the CardViewModel which in turn calls its own PrintCommand. That latter command calls
_printService.ShowGridViewPrintPreview(View);
where the View is a CardViewModel's property that is set upon CardView loading with e.g.
<dxmvvm:Interaction.Behaviors>
<dxmvvm:EventToCommand EventName="Loaded" Command="{Binding ViewLoadedCommand}" CommandParameter="{Binding ElementName=PersonsCardView}" />
</dxmvvm:Interaction.Behaviors>
Because I have two child views I want to print, I'd need to add a view model for each one of those. In addition, those two view models plus the PersonViewModel need access to the list of Persons to be printed. In particular, they need a shared access to the same data, so that they are synchronized. A simple way to do that is explained here and is totally doable. But I think it is not worth the trouble for the simple use case I have as it adds more complexity than necessary.
I have a property in a view model which I would like to be able to set via the XAML but I can't figure out how to do it.
I have a pretty basic user control (containing a list of items), two of which are to be placed on a page and I would like to be able to set one to be a 'Source' (defined by an enum) and one to be a 'Target'.
[The code below has been stripped down quite a bit so apologies if I've accidentally made some mistakes or missed something out.]
My enumeration is:
public enum ConversionSide
{
Source, // Convert something FROM whatever is here.
Target // Convert something TO whatever is here.
}
I have a page which looks like this:
<Page
x:Class="MyApp.Views.ConverterPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:models="using:MyApp.Models"
xmlns:my="using:MyApp.Controls"
xmlns:prismMvvm="using:Prism.Windows.Mvvm"
prismMvvm:ViewModelLocator.AutoWireViewModel="True"
Style="{StaticResource PageStyle}"
mc:Ignorable="d">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<my:SelectorPage Name="SourceSelector" Grid.Column="0" />
<my:SelectorPage Name="TargetSelector" Grid.Column="1" />
</Grid>
</Page>
...where SelectorPage is a user control (I've called it a 'Page' to make the Prism AutoWire work but that's not the issue here) containing a list of items (all working fine) which looks like this...
<UserControl
x:Class="MyApp.Controls.SelectorPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:models="using:MyApp.Models"
xmlns:my="using:MyApp.Controls"
xmlns:prismMvvm="using:Prism.Windows.Mvvm"
prismMvvm:ViewModelLocator.AutoWireViewModel="True"
mc:Ignorable="d">
<ListView
Grid.Column="0"
ItemsSource="{x:Bind ViewModel.MyList, Mode=OneWay}"
SelectedItem="{x:Bind ViewModel.MySelectedItem, Mode=TwoWay}">
<ListView.Header>
<TextBlock Margin="0,8,0,8" HorizontalAlignment="Center" FontStyle="Italic" Text="Header Text" />
</ListView.Header>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate x:DataType="models:MyListItem">
<my:MyListItemTemplate />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</UserControl>
..with code behind as...
public sealed partial class SelectorPage : UserControl
{
private SelectorViewModel ViewModel => DataContext as SelectorViewModel;
public SelectorPage()
{
this.InitializeComponent();
}
}
SelectorViewModel looks like this...
public class SelectorViewModel : ViewModelBase
{
private ConversionSide _side;
public ConversionSide Side
{
get { return _side; }
set { SetProperty(ref _side, value); }
}
// Many lines have been omitted for 'clarity'.
}
I would like to be able to set the Side property of SelectorViewModel in XAML like this...
<my:SelectorPage Name="SourceSelector" Grid.Column="0" Side="Source" />
<my:SelectorPage Name="TargetSelector" Grid.Column="1" Side="Target" />
(Once Side has been set, I do not expect it to ever change.)
How can I do this?
I've looked at using a dependency property but I can't get it to change the property in SelectorViewModel. When I add one in SelectorPage it's visible in the XAML and I can set it but it doesn't actually do anything so I'm probably not using it right. Putting a dependency property in the view model doesn't sound right to me but I could be wrong.
I've had a look around the web - Microsoft documentation, blogs, articles, stack overflow, etc. - but I can't find anything that explains things well enough for me to figure out what I'm supposed to do. The writings I've found seem to be exclusively about getting information from a bound property - which I'm okay with - but what I'm after is setting a property from the XAML.
Can anyone give my any clues please? I don't know if I'm just a tiny step away from getting what I want or if I'm miles away.
This would set the Side property of the SelectorPage control to Source:
A view sets the property of a view model by two-way bind to it. For example, the following TextBox sets the string property of a view model called Test when you change the text in the TextBox:
<TextBox Text="{Binding Test, Mode=TwoWay}" />
So setting the property of a view model from the view typically applies to controls that handles some kind of input. Any default value of a source property should be defined in the view model:
private ConversionSide _side = ConversionSide.Source;
You shouldn't define the default values in the view.
i know that silverlight already has a child window control , but i want to use this child window control from my own library.
specifically i want the code to look something like this:
XAML:
<mycontrols:myChildWindow x:Class="SilverlightClassLibrary1.ChildWindow1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mycontrols="clr-namespace:mynamespace;assembly=myassembley"
Width="400" Height="300"
Title="ChildWindow1">
<Grid x:Name="LayoutRoot" Margin="2">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Button x:Name="CancelButton" Content="Cancel" Click="CancelButton_Click" Width="75" Height="23" HorizontalAlignment="Right" Margin="0,12,0,0" Grid.Row="1" />
<Button x:Name="OKButton" Content="OK" Click="OKButton_Click" Width="75" Height="23" HorizontalAlignment="Right" Margin="0,12,79,0" Grid.Row="1" />
</Grid>
and my project would reference myassembly.dll , which would have mynamespace in it.And there would be class myChildWindow in mynamespace.This class can inherit from System.windows.control.childwindow (possibly).
I know this is a weird implementation way.But i need it to be like this. please tell me how would i implement myChildWindow class?
If the question is not clear ,please ask further questions.i can make the edits in the question.
You need 2 things.
1. Create class derived from ChildWindow
namespace mynamespace
{
public class myChildWindow : ChildWindow
{
public myChildWindow():base()
{
//Add custom constructor code
}
}
}
2. In XAML change
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
to
xmlns="http://schemas.microsoft.com/client/2007"
Look at example with XAML body:
<mycontrols:myChildWindow x:Class="Project.Views.EditReport"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mycontrols="clr-namespace:mynamespace;assembly=myassembley"
Width="400"
Title="Edit Report"></CWindow>
It seems to me as though you have already answered your own question. As mentioned above, you can inherit from ChildWindow. But, after that, what you need to do is include a reference to the assembly with this class in it in your Silverlight project. Once you do that, the assembly will be added to the AppManifest, the DLL will be included in the Xap package, and you will be able to reference it in Xaml as you already have done.
The namespace above is incorrect thought. It should be:
namespace mynamespace
{
public class myChildWindow : ChildWindow
{
public myChildWindow():base()
{
//Add custom constructor code
}
}
}
It should be compiled in to the assembly named "myassembley". However, you can't use x:Class in your Xaml if you are referencing another assembly.
I'd like to switch only some part of my View (which is UserControl) xaml.
For example, I'd like to be able to change only 2nd Grid.
<Grid> //main grid
<Grid Name="1" Grid.Row="1"/>
<Grid Name="2" Grid.Row="2"/>
</Grid
I've tried sth like this:
<UserControl.Resources>
<ControlTemplate x:Key="UsualMode">
<Grid>
...
</Grid>
</ControlTemplate>
</UserControl.Resources>
<Grid> //main grid
<Grid Name="1" Grid.Row="1"/>
<ControlTemplate Name="2" Grid.Row="2" Template="{StaticResource UsualMode}"/>
</Grid>
Then, by using triggers and binding I would be able to switch templates.
Unfortunately, this doesn't work for me due to 'Bootstrapper.cs not found' exception.
How should I do that? I cannot use conductor -> have to load only one View.
http://caliburnmicro.codeplex.com/wikipage?title=All%20About%20Conventions
Read up on the basics of view resolution
Basically you would create the following in your view:
<UserControl.Resources>
<ControlTemplate x:Key="UsualMode">
<Grid>
...
</Grid>
</ControlTemplate>
</UserControl.Resources>
<Grid> //main grid
<Grid Name="1" Grid.Row="1"/>
<ContentControl x:Name="ChildViewModel" cal:View.Context="{Binding ContextBinding}" />
</Grid>
Your parent viewmodel needs to have a 'context' property and a property to house the child VM:
public class ParentViewModel
{
public SomeViewModel ChildViewModel { get; private set; }
public string ContextBinding { get; private set; } // make sure you implement INPC on these properties as is the usual
}
Your view will then be resolved based on the ContextBinding string (as per the CM conventions above).
So if you were to update the string:
ContextBinding = "DetailedView";
CM would then update the UI and try to look for a view called DetailedView in a subnamespace of the current VMs namespace
If you don't want to have a child VM, you can actually get CMs conventions to kick in earlier and apply a context over the current VM, but in this case you would need to create two views which were almost identical apart from the area which you would like to 'swap out'.
My preference would be to create a child VM to handle the sub-area that will swap views as I've shown above