I have a command button that launches a separate window. I'm using the MVVM Cross architecture, and so the window is fed through a command in the ViewModel. The way that I've done this is a register a service in the UI layer that displays a window, and then in the command, to resolve that service and display the window:
public static void ShowMyWindow()
{
IShowMyWindowService myService = Mvx.Resolve<IShowMyWindowService>();
myService.ShowWindow();
}
Then, in the service (which is in the UI layer):
public void ShowWindow()
{
Window myWindow = new Window();
myWindow.Content = new MyUserControl();
Application.Current.Dispatcher.Invoke(() =>
{
myWindow.Owner = Application.Current.MainWindow;
// Need to set x, y to the position of the button that invoked the command
myWindow.Left = x;
myWindow.Top = y;
myWindow.ShowDialog();
});
}
The problem that I have is that I want to show the new dialog in a position relative to the position of the command button that launched it. Having done a bit of research, it looks like I need the FrameworkElement.PointToScreen() method, although what I can't determine is how to access this information without breaking the separation of concern.
Am I going about this the correct way, or is there an easier way? If i am, then how can I pas the framework element through the command?
You should have a method / command, as such:
public static void ShowMyWindow(WindowAbsoluteLocationPosition pos)
{
IShowMyWindowService myService = Mvx.Resolve<IShowMyWindowService>();
myService.ShowWindow(pos);
}
If that is done, you should use CommandParameter with Converter:
<Button Command="{Binding YourCommand}"
CommandParameter="{Binding ElementName=YourWindow,
Converter={StaticResource yourConverter}}" />
which basically can turn YourWindow into the correct WindowAbsoluteLocationPosition (user-defined) structure that is passed to your viewModel. Notice; you should also pass your button to the converter, in order to have better "reusability".
In this scenario, I would propose the following:
Create another method under the same name which has an additional parameter which represents the parent window:
public void ShowWindow(Window parentWindow)
{
//Access the parentWindow location properties to work out a relative position to show the dialog
}
You will need to also overload the ShowMyWindow method in the service with the same parameter to call this new ShowWindow method.
Related
I want to create an application where the user can enter shortcuts to files. I know how to create buttons in code at compile time but I have no idea how to create dynamic buttons where the name and the click event are going to be dynamic.
How hard would it be to create something like the image below? Is that even possible in C#/WPF/XAML?
What would be the logic?
FYI - I don't need help with saving the buttons objects, for this I will be using JSON.
You should create an ItemsControl to show what you want, this could be an approach:
<ItemsControl
ItemsSource="{Binding YourListOfLinkObject}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding WhateverYouWantToShow}"
Command="{Binding YourCommand} "
CommandParameter="{Binding YourFileName}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
You should create a new (if it's not already created) class with the name of the file, the content you want to show in the button and your command. And when initializing the view, create a list of "Link" object.
The command will be the same for all of them, just declare it in a generic way to open the file you put in the CommandParameter
Now that I know you are using MVVM I will try to expand my answer focus on that.
You need a class that I will call FileLink. FileLink will have, at least, 3 properties:
public string WhateverYouWantToShow - This will be the content of your button
public ICommand YourCommand - This will have a DelegateCommand<string> that will be the one who "does" things. This command will be the same for every item you create. You just need one because you will use the parameter to execute/open one file or another.
public string YourFileName - This will be the string you need to execute your command method. I guess it will be a path or a file name.
Now that we have this class created, when initializing the third view, the one with the buttons, you will have an ObservableCollectionproperty, what I called YourListOfLinkObject, of FileLinkobjects. There you will have to add as many FileLink objects as you got from the database and they will be displayed.
If you need to change the way they are shown you just need to modify the DataTemplate.
If there's something I failed to explain again or you want me to go further just let me know :)
It is possible and simple. You add controls to your container and add container to main form. Click event is simply defined in code (which actually you know at dev time - probably you should instead use user controls).
Below is some partial code, doing a similar thing in a real world Silverlight application from years ago to give the idea:
...
sp.Children.Add(p);
foreach (var slot in group)
{
var color = colors[(int)slot.State];
var name = String.Format("W{0}", slot.When.ToString("yyyyMMddHHmm"));
Rectangle rect = new Rectangle
{
Name = name,
Width = rectWidth,
Height = rectWidth,
Margin = new Thickness(rectMargin),
Stroke = new SolidColorBrush(slot.State == Availability.Booked ? Colors.White : Colors.Black),
StrokeThickness = 1,
Fill = new SolidColorBrush(color),
RadiusX = 2,
RadiusY = 2,
Cursor = (slot.State == Availability.Booked ? Cursors.Arrow : Cursors.Hand)
};
if (slot.State != Availability.Booked)
{
rect.Effect = new DropShadowEffect(); //myDropShadowEffect,
}
if (slot.State != Availability.Booked)
{
rect.MouseLeftButtonDown += new MouseButtonEventHandler(rect_MouseLeftButtonDown);
ToolTipService.SetToolTip(rect, slot.When.ToString("MMM dd,yyyy dddd hh:mm tt"));
}
sp.Children.Add(rect);
}
b.Child = sp;
contentStackPanel.Children.Add(b);
}
I want to rebind the "Up" key for my window to perform a command on my ViewModel.
My window contains 2 controls: ListView, TextBox.
If I do
<Window.InputBindings>
<KeyBinding Key="F5" Command={Binding SomeCommand} />
</Window.InputBindings>
Everything works correctly. However, if I set it to "Up", or certain other keys, the command does not get executed if the TextBox has focus. This tells me that the TextBox is handling these keys and swallowing the event that triggers the command. Is there any way to prevent that and to allow the window to get them similar to the Preview events (PreviewKeyUp) ?
If you want an application level shortcut, you can use InputManager
InputManager.Current.PreProcessInput += (sender,args)
{
var keyboardEvent = args?.StagingItem?.Input as KeyboardEventArgs;
if(keyboardEvent != null)
{
var keyArgs = keyboardEvent.RoutedEvent as KeyEventArgs;
if(keyArgs != null)
{
if(keyArgs.Key == Key.F5)
{
// Do something
}
}
}
};
I would recommend taking the logic above and incorporating it into a behavior that you can bind/map Keys to Commands.
Is there any way to prevent that and to allow the window to get them similar to the Preview events (PreviewKeyUp) ?
You could hook up an event handler to the KeyDownEvent using the overload of the AddHandler method that accepts a "handledEventsToo" boolean parameter and invoke your command from this event handler:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var vm = new ViewModel();
DataContext = vm;
AddHandler(KeyDownEvent, new KeyEventHandler((ss, ee) =>
{
vm.SomeCommand.Execute(null);
}), true);
}
}
There is no way to do this in pure XAML though but it doesn't really matter as far as the MVVM pattern is concerned as you simply move the invocation of the command from the XAML markup of the view to the code-behind class of the very same view. This doesn't break the MVVM pattern.
I have a small design question which I couldn't find relevant google hits for some reason.
I have a user control which I use in my application.
The main form opens a second form as a dialog. T
his second form is using the user control which includes a list box.
Naturally I want to preserve the list box items when the forms dispose so I am keeping a private list in the main form.
List<string> _listofFirstCoordinates = new List<string>();
Now the question is, should the dialog form be the one responsible for relaying the list to the main form or should the code be in the user control?
Should the one populating the list be the user control
lst_Coordinates.Items.AddRange(ListOfCoordinates.Cast<object>().ToArray());
or should the form using it populate it (The subform)
uc_EditCoordinates.ListOfCoordinates = ListOfCoordinates;
Also is it feasible to just have the user control be a public variable for the form holding it so it may be changed directly or would that be bad design?
Edit:
By the way, the data is saved for now in variables going back and forth between the forms as the user has to finish all subforms before submitting and finally saving it to the database. So it is a
var _listofFirstCoordinates = new List<string>();
going back and forth.
The "correct" solution is to abstract-away the View-level concern (in this case, anything to do with Form, UserControl, and UI controls) away from the Controller and Model-level concerns (in this case, your application's data).
Without completely rearchitecturing your system, you can still apply this separation-of-concerns within your example.
You can conceptually argue the "code-behind" of your MainForm class acts as a kind of Controller (purists would disagree). It will have to know about creating the child form, but it does not need to know about the user-control hosted within the child form - that would be the concern of the child form's.
I suggest defining a class that represents a ViewModel - albeit as we're using WinForms we will use it as a kind of crude "one-way" ViewModel, like so:
class MainForm : Form {
private void ShowChildFormModal() {
ChildViewModel vm = new ChildViewModel();
vm.CoordinatesList = ...
vm.OtherData = ...
ChildForm child = new ChildForm();
child.LoadFromViewModel( vm );
child.ShowDialog();
child.SaveToViewModel( vm );
SaveToDatabase( vm );
}
}
class ChildViewModel { // this is a POCO
public List<String> CoordinatesList;
public Int32 OtherData;
}
class ChildForm : Form {
public void LoadFromViewModel(ChildViewModel vm) {
// save time and trouble by using the List as a datasource directly, or you can manually populate the combobox as well
this.childUserControl.LoadFromViewModel( vm );
this.someOtherControl.Value = vm.OtherData;
}
public void SaveToViewModel(ChildViewModel vm) {
// completing this is an exercise for the reader
// but basically copy values from the controls on the form into the `vm` instance
}
}
class ChildUserControl : UserControl {
public void LoadFromViewModel(ChildViewModel vm) {
this.comboBox.DataSource = vm.CoordinatesList;
}
}
I am trying to execute a bound command from my code behind utilizing the UiElement. button.Command.Execute(button.CommandParameter)
However, at this point the Command property of the button is null. simultaneously when I check the command in my View Model the property is set. The only diagnosis I can come up with is that until the window is actually visible the command is not bound to the command property of the button. I feel like may I'm missing a step somewhere or my implementation is not sound. below is some snipits of the code, please let me know if you need more.
Window constructor:
public PlottingViewModel ViewModel { get; set; }
public PlottingGUI()
{
InitializeComponent();
DataContext = (ViewModel = new PlottingViewModel());
_setDefaultSelections();
}
IList<RadioButton> buttons;
Setting default selections:
private void _setDefaultSelections()
{
buttons = new List<RadioButton>();
_getRadioButtons(this);
foreach (var setting in ViewModel.Settings.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
{
var settingValue = setting.GetValue(ViewModel.Settings);
var button = buttons.FirstOrDefault(btn => btn.Content.Equals(settingValue)
|| ((string)btn.CommandParameter).Equals(settingValue));
if (button == null)
continue;
button.IsChecked = true;
// NullReference here
// button.Command.Execute(button.CommandParameter);
}
}
one of the RadioButtons XAML:
<RadioButton Content="None"
Grid.Row="0"
Command="{Binding StampedCommand}"
CommandParameter="None"
Foreground="WhiteSmoke"/>
I feel, the only way i may be able to successfully complete this task is to execute the command directly from my viewmodel. (Which i don't want to do)
Thanks for reading..
To sum up comments at the point when you're calling _setDefaultSelections() bindings have not been updated yet, hence Command is still null, so you have to wait until everything is loaded. You can call _setDefaultSelections during Loaded event
Occurs when the element is laid out, rendered, and ready for interaction.
Relative new to MVVM - I realize this is simple question but can't seem to find the answer.
I have 4 grouped radio buttons that when one is selected will show its associated options. I assume these 4 radio buttons should be linked to the same viewmodel command which in this case is called UpdateIndex.
How do I determine which of the radio buttons is calling the UpdateIndex so that I can take appropriate action and show the appropriate options?
Note that my UpdateIndex and UpdateIndexExecute does get called correctly from my radio button command binding, I just don't know how to determine which radio button is calling it. I assume it has to do with CommandParameter - but not sure how to access it from the viewmodel.
An example of my radio button:
<RadioButton
Content="Option 1"
GroupName="GroupHeader"
Command="{Binding UpdateIndex}" />
Code snippet of my command being called from the radio button when clicked...
void UpdateIndexExecute()
{
}
bool CanUpdateIndex()
{
return true;
}
public ICommand UpdateIndex
{
get
{
return new RelayCommand(UpdateTabIndexExecute, CanUpdateTabIndex);
}
}
In a true MVVM implementation, you won't know which RadioButton executed the command, because the ViewModel should not have any information about the View. User Controls fall squarely in the category of "things that only exist within the View, not the ViewModel." You should instead pass something meaningful to the ViewModel.
You are correct there are ways to pass information into an ICommand using the "CommandParameter" of a Command Binding. For that, you would use the "generic" form of the RelayCommand (RelayCommand) class, where "T" represents the type of object you are passing as a parameter.
If you're just trying to pass an index as a parameter, I imagine it'll look something like this:
<!-- We are passing index "1" as a parameter -->
<RadioButton Content="Option 1" GroupName="GroupHeader"
Command="{Binding UpdateIndex, CommandParameter=1}"/>
Then in your ViewModel:
void UpdateIndexExecute(int index)
{
}
bool CanUpdateIndex(int index)
{
return true;
}
public ICommand UpdateIndex
{
get
{
return new RelayCommand<int>(UpdateTabIndexExecute, CanUpdateTabIndex);
}
}
Instead of binding the command, you can bind the content, use the INotifyPropertyChanged interface to handle changes made by the control.