I'm using mvvmlight framework for wpf.
I have main window where I put my frame (firstLoadedPage is just an example):
<Frame Source="firstLoadedPage" />
I want to change my page to another one when I click button in
first loaded page
but I have no idea how can I achieve it.
I've tried bind property like this to frame source:
public static string _myFrameSourcePath = firstLoadedPage
public string MyFrameSourcePath
{
get { return _myFrameSourcePath; }
set {
_myFrameSourcePath = value;
RaisePropertyChanged("MyFrameSourcePath");
}
}
and then change value of _myFrameSourcePath when I click button in firstLoadedPage. It doesn't work so I tried change MyFrameSourcePath to static and then change value of MyFrameSourcePath instead of _myFrameSourcePath in Page. It also doesn't work.
Can somebody tell me how can I change my page placed in frame source in MainWindow when I click button inside this page to change current page to another?
You can do it this way. Below is a sample Xaml
<Grid>
<StackPanel Orientation="Vertical">
<TextBlock>Outside area of frame</TextBlock>
<Frame Name="FrameWithinGrid" Source="{Binding FrameSource}">
</Frame>
<Button x:Name="button1" Height="23" Margin="114,12,25,0" Command="{Binding GoToCommand}"
VerticalAlignment="Top" >Navigate to Msdn
</Button>
</StackPanel>
</Grid>
In Your ViewModel , some sample codes for example:
private Uri _frameSource = new Uri("http://www.google.com", UriKind.Absolute);
public Uri FrameSource
{
get { return _frameSource;}
set
{
_frameSource = value;
OnPropertyChanged("FrameSource");
}
}
public ICommand GoToCommand
{
get
{
return new DelegateCommand(ExecuteGoTo);
}
}
private void ExecuteGoTo()
{
FrameSource = new Uri("http://www.msdn.com", UriKind.Absolute);
}
Thats all. Make sure your view model implements INotifyPropertyChanged. If you are using MVVM Light, change the DelegateCommand to RelayCommand
Related
I'm currently building the UI for the app I'm working on and I have a few problems with the bindings.
The Scenario:
I have a pivot control with every pivot element consisting of an extra Frame/Page.
Now I have a TextBlock on the first PivotItem. I bind this to a "string" and use a button to switch between two possible contents of the button.
When the button is on the same Page/Frame it works like a charm. But when I implement a button on the MainPage and implement the same Viewmodel for the MainPage then it doesn't work. It will only change the string content on the MainPage.
Is it possible to implement the change for every Page/Frame?
And when that is done I have a Page where I gather data with a serial port.
I save the data to a List and I want to be able to use this list from 2 different Pages/Frames.
Thinking about the scenarion above then it would probably gather the data for the page where I have the button to get the data but it would probably display nothing on the other page.
How can I build it like I want it to be?
Here is a short example:
Mainpage.xaml
<StackPanel>
<Button Height="50" Width="200" Content="Change" FontSize="30" FontWeight="Bold" Margin="50 50 0 0" Click="{x:Bind MainViewModel.Change}"/>
<Pivot x:Name="MainPivot" Margin="50 50">
<PivotItem Header="Page 1">
<Frame x:Name="Page1" />
</PivotItem>
</Pivot>
</StackPanel>
Mainpage.xaml.cs
public MainPage()
{
this.InitializeComponent();
Page1.Navigate(typeof(Page1));
ViewModel = new MainViewModel();
}
public MainViewModel ViewModel { get; private set; }
Page1.xaml
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Text="{x:Bind Path=ViewModel.StringModel.String1, Mode=TwoWay}" FontSize="50" FontWeight="Bold" />
<Button Content="Change" FontSize="30" FontWeight="Bold" Click="{x:Bind ViewModel.Change}"/>
</StackPanel>
Page1.xaml.cs
public Page1()
{
this.InitializeComponent();
ViewModel = new MainViewModel();
}
public MainViewModel ViewModel { get; private set; }
MainViewModel.cs
private StringModel _stringModel = new StringModel();
public StringModel StringModel
{
get => _stringModel;
set
{
if (_stringModel != value)
{
_stringModel = value;
OnPropertyChanged();
}
}
}
public void Change()
{
if (StringModel.String1 == "Text1")
{
StringModel.String1 = "Text2";
}
else
{
StringModel.String1 = "Text1";
}
}
StringModel.cs
private string _string1 = "XXX";
public string String1
{
get => _string1;
set
{
_string1 = value;
OnPropertyChanged();
}
}
Sounds like you are missing a "service layer" or "business layer" of your application. You need an external class which manages the data, and can provide models to populate your ViewModels:
I'd suggest using some kind of dependency injection, so each of your page view models have a reference to the DataProvider service class. This class does the serial port work to get a list of models, and provides an interface for getting data and pushing any updates to the ViewModels.
A good way of handling events that are shared, like say a "load data" button that may appear on different view models is an Event Aggregator. A service that can be injected into classes where events can be raised or subscribed to across the application.
Generally children of a XAML parent inherit the binding context of said parent.
So not sure you need to hook up a VM to your frame.
But suppose it does not work with Frames, you are creating a new MainViewModel for the frame as for the mainpage!
The solution here would be to create a singleton MainViewModel and get a hold of that one to hook up the BindingContext.
You can use Publisher-Subscriber pattern (Pub-Sub).
Already explained this here: Communication Between Views in MVVM (Pub-Sub Pattern)
I created a user control that looks like a tile. Created another user control named TilePanel that serves as the default container of the tiles. And lastly, the very UI that looks like a Window start screen. I used RelayCommand to bind my TileCommands
Here are the codes:
Tilev2.xaml
<UserControl x:Class="MyNamespace.Tilev2"
Name="Tile"....
>
...
<Button x:Name="btnTile" Style="{StaticResource TileStyleButton}" Command="{Binding ElementName=Tile, Path=TileClickCommand}" >
</Button>
</UserControl>
Tilev2.xaml.cs
public partial class Tilev2 : UserControl
{
public Tilev2()
{
InitializeComponent();
}
//other DPs here
public ICommand TileClickCommand
{
get { return (ICommand)GetValue(TileClickCommandProperty); }
set { SetValue(TileClickCommandProperty, value); }
}
// Using a DependencyProperty as the backing store for TileClickCommand. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TileClickCommandProperty =
DependencyProperty.Register("TileClickCommand", typeof(ICommand), typeof(Tilev2));
}
}
Then I created a TilePanel user control as the container of the tiles
TilePanel.xaml
<UserControl x:Class="MyNamespace.TilePanel"
...
>
<Grid>
<ScrollViewer>
<ItemsControl Name="tileGroup"
ItemsSource="{Binding TileModels}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local2:Tilev2 TileText="{Binding Text}"
TileIcon="{Binding Icon}"
TileSize="{Binding Size}"
TileFontSize="{Binding FontSize}"
Background="{Binding Background}"
TileCaption="{Binding TileCaption}"
TileCaptionFontSize="{Binding TileCaptionFontSize}"
TileClickCommand="{Binding TileCommand}"
/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
</UserControl>
TilePanel.xaml.cs
public partial class TilePanel : UserControl
{
public TilePanel()
{
InitializeComponent();
DataContext = new TilePanelViewModel();
}
public TilePanelViewModel ViewModel
{
get { return (TilePanelViewModel)this.DataContext; }
}
}
My ViewModel for TilePanel
TilePanelViewModel.cs
public class TilePanelViewModel : ViewModelBase
{
private ObservableCollection _tileModels;
public ObservableCollection<TileModel> TileModels
{
get
{
if (_tileModels == null)
_tileModels = new ObservableCollection<TileModel>();
return _tileModels;
}
}
}
Then my Tile model
TileModel.cs
public class TileModel : BaseNotifyPropertyChanged
{
//other members here
ICommand tileCommand { get; set; }
//other properties here
public ICommand TileCommand
{
get { return tileCommand; }
set { tileCommand = value; NotifyPropertyChanged("TileCommand"); }
}
}
}
This is my StartScreen View where TilePanels with tiles should be displayed...
StartScreen.xaml
<UserControl x:Class="MyNamespace.StartMenu"
... >
<Grid>
<DockPanel x:Name="dockPanel1" Grid.Column="0" Grid.Row="1" Margin="50,5,2,5">
<local:TilePanel x:Name="tilePanel"></local:TilePanel>
</DockPanel>
</Grid>
</UserControl>
StartScreen.xaml.cs
public partial class WincollectStartMenu : UserControl, IView<StartMenuViewModel>
{
public WincollectStartMenu()
{
InitializeComponent();
}
public StartMenuViewModel ViewModel { get { return (DataContext as StartMenuViewModel); } }
private void UserControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
ViewModel.Tile = tilePanel.ViewModel.TileModels;
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
return;
}
}
In my start screen ViewModel, I used ObservableCollection Tile
and use Tile.Add(tile); to populate my start screen with Tiles inside the TilePanel...
StartMenuViewModel.cs
TileModel tile = new TileModel() { Text = "Testing1", FontSize = 11, Size = TileSize.Medium, Background = (SolidColorBrush)new BrushConverter().ConvertFromString("#039BE5"), Tag="Something" };
tile.TileCommand = new RelayCommand(
p => Tile_TileClick(tile.Tag),
p => true
);
temp.Add(tile);
Now the problem is, if I add a new code below, tile = new TileModel() {...}
tile.TileCommand = new RelayCommand(...), even if I clicked on the first tile, my Tile_TileClick() will get the second tile's info (or the last tile inserted)...
Am I doing something wrong? Or Im doing everything wrong...?
This is not direct answer to your question, but hopefully it will give you few thoughts.
Ok, first of all, don't name your usercontrol like this:
<UserControl x:Class="MyNamespace.Tilev2" Name="Tile"/>
because the name can be easily overriden when using the usercontrol somewhere:
<local:Titlev2 Name="SomeOtherName" />
and the binding inside Tilevs with ElementName won't work: Command="{Binding ElementName=Tile, Path=TileClickCommand}"
Second, what's the point of Tilev2 usercontrol? Why don't just put the button directly to the DataTemplate inside TilePanel class?
If you need to reuse the template, you can put the template to resource dictionary.
If you need some special presentation code in the Tilev2 codebehind or you need to use the Tilev2 without viewmodel, it's better to create custom control instead of usercontrol in this case. it has much better design time support, and writing control templates it's easier (Triggers, DataTriggers, TempalteBinding, etc). If you used custom Control insead UserControl, you wouldn't have to write {Binding ElementName=Tile, Path=TileClickCommand}, or use RelativeSource, etc.
Third, it seems like you forced MVVM pattern where you can't really take advantage of it. Point of MVVM is separate application logic from presentation. But your Tile and TilePanel usercontrols are just presentation. You application logic could be in StartScreen which is concrete usage of TileName.
I would create custom controls called TilePanel (potentionally inherited from ItemsControl, Selector or ListBox) and if needed also for Tile. Both controls should not be aware of any viewmodels. There's absolutelly no need for that.
Take ListBox as an example. ListBox does not have viewmodel but can be easily used in MVVM scenarios. Just because ListBox it is not tied to any viewmodel, it can be databound to anything.
Just like ListBox creates ListBoxItems, or
Combobox creates ComboBoxItems, or
DataGrid creates DataGridRows or
GridView (in WinRT) creates GridViewRow, your TilePanel could create Tiles.
Bindings to tile specific properties, like Icon or Command could be specified in TilePanel.ItemContainerStyle orusing simillar appriach like DisplayMemberPath, resp ValueMemberPath in ListBox.
final usage could the look like:
<TilePanel ItemsSource="{Bidning ApplicationTiles}" />
or
<TilePanel>
<Tile Icon=".." Command=".." Text=".." />
<Tile Icon=".." Command=".." Text=".." />
</TilePanel>
Last, the name `TilePanel' evoked that it is some kind of panel like StackPanel, WrapPanel, etc. In other words, it is FrameworkElement inherited from Panel.
TilesView would be more suitable name for the control than TilePanel. The -View postfix is not from MVVM, it just follows naming convention -GridView, ListView...
Saw the problem...
To pass a parameter from button, I used CommandParameter so I could use it in switch-case scenario to know which button was clicked. But still, param was still null...
<Button x:Name="btnTile" Style="{StaticResource TileStyleButton}" CommandParameter="{Binding}" Command="{Binding Path=TileClickCommand, ElementName=Tile}" >
</Button>
TileCommand = new MyCommand() { CanExecuteFunc = param => CanExecuteCommand(), ExecuteFunc = param => Tile_TileClick(param)}
After 2 whole damn days, I changed it:
From this:
<UserControl Name="Tile"...>
<Button x:Name="btnTile" Style="{StaticResource TileStyleButton}" CommandParameter="{Binding Tag, ElementName=Tile}" Command="{Binding Path=TileClickCommand, ElementName=Tile}" >
</Button>
</UserControl>
To this:
<UserControl Name="Tile"...>
<Button x:Name="btnTile" Style="{StaticResource TileStyleButton}" CommandParameter="{Binding}" Command="{Binding Path=TileClickCommand, ElementName=Tile}" >
</Button>
</UserControl>
My first post does error because CommandParameter does not know where to get its DataContext so I replaced it to CommandParameter={Binding} so it will get whatever from the DataContext.
I need some help and I hope you can help me. I have a complex usercontrol with a method that changes the color of all elements inside. When I try to connect it with a method stub in the MainWindow-Code-behind, I can fire it up easily. I want to use MVVM in the future so now I want to connect it to a button in the main window through commands.
So here's my ViewModel and my MainWindow.cs
public class ViewModel
{
public DelegateCommands TestCommand { get; private set; }
public ViewModel()
{
TestCommand = new DelegateCommands(OnExecute, CanExecute);
}
bool CanExecute(object parameter)
{
return true;
}
void OnExecute()
{
//testUC.NewColor(); HERE I WANT TO START THE UC-METHOD
}
}
public partial class MainWindow : Window
{
ViewModel _ViewModel = null;
public plate tplate;
public MainWindow()
{
InitializeComponent();
_ViewModel = new ViewModel();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
tplate = new plate();
}
}
On my MainWindow-View I have a simple button and the usercontrol.
<exte:plate x:Name="testUC" Grid.Column="1"/>
<Button Content="Button" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Margin="43,247,0,0" Command="{Binding TestCommand}"/>
I want to start the UC-Method in the OnExecute()-Method but I'm not able to select the "testUC" because it's not available in this context.
Is there an easy way to start the UC-Methods through command bindings?
Thanks for any help.
Timo
How to solve you're binding problem. First, most bindings are related to the most specific DataContext. Means, you have to set your control as such. E.g.
public class MySpecialButton : Button
{
public MySpecialButton()
{
DataContext = this; // there are other possibilties, but this is the easiest one
}
}
With this you can bind every command implemented in MySpecialButton.
Another possibility is to use bindings with relative source. E.g.
<Button Command="{Binding TheCmd, RelativeSource={AncestorType={x:Type MySpecialButton}}}" />
You could even declare the DataContext with the method in the example above.
Hope this helps you.
Ok, I tried it the latter way you described ("Button" is the Button I want to use to trigger the method, "NewPlate" is the method-name and exte:plate is the customcontrol-type):
<Button Content="Button" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Margin="43,247,0,0" Command="{Binding NewPlate, Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type exte:plate}}}" />
But again, nothing happens.
To understand your first suggestion correctly, I have to declare my Custom-Control as a Button instead of a user control? I do not understand, where I have to put the datacontext stuff. I'm just using a standard wpf-button to trigger the custom-control-method. I do not have any class for this button where I can declare a datacontext.
I use MVVM. I have a ViewA with a Grid in xaml and a ViewModelA. In the ViewModelA I have a Method that looks like this which is called on a Button click:
public void ButtonClickMethod()
{
ViewB viewB = new ViewB();
viewB.DataContext = new ViewBViewModel();
}
How do I add all the created viewBs into the Grid on my ViewA, so I can see them there? Or is my solution wrong in general maybe?
EDIT :
I now used a ObservableCollection<ViewB> in my ViewModelA which notifies on changes in my ViewA xaml at
<ItemsControl ItemsSource="{Binding ObservableCollection<ViewB>}" />
The only problem is the ViewBs shown in the ItemControl should be dragable. Therefore I used expression blend's
Interaction.GetBehaviors(ViewB).Add(new MouseDragElementBehavior() { });
when i create a new ViewB. But it doesn't work.
EDIT2 :
I tried this solution but it doesn't work for me :( Using MouseDragElementBehavior with an ItemsControl and Canvas
Add your Grid an ContentControl and bind it content to your second view.
Here an example with an UserControl.
<ContentControl Grid.Column="0" Margin="5" Content="{Binding SecondView}"/>
Code im ViewModel:
private UserControl secondView;
public UserControl SecondView
{
get
{
return secondView;
}
set
{
this.SetProperty(ref secondView,value);
}
}
I'm rewriting a project in WPF using Caliburn framework. I came from C++ world, so have some difficulties figuring out even simplest things...
So, let's say I have: MainView, MainViewModel, DialogView, DialogViewModel.
In MainView.xaml:
...
<MenuItem Name="Dialog" Header="Dialog"></MenuItem>
...
Caliburn bounds it to a method in MainViewModel:
public void Dialog()
{
dynamic settings = new ExpandoObject();
settings.WindowStartupLocation = WindowStartupLocation.Manual;
_windowManager.ShowWindow(new DialogViewModel(_windowManager), null, settings);
}
It works fine, Dialog pops up.
Now, in this dialog I have:
<TextBox Name="Dimension1"/>
<TextBox Name="Dimension2"/>
plus, other textboxes, checkboxes etc.
Then there are OK and Cancel buttons:
<Button Content="OK" Name="OK"></Button>
<Button Content="Cancel" Name "Cancel"></Button>
Now, as it is right now they are bound to OK() and Cancel() methods in DialogViewModel and I cannot figure out or find information on how to deal with them in DialogViewModel.
I found an example when DialogResultsAction class is created, I can bound my OK/Cancel buttons with the methods in this class, but can't understand how to proceed further...
Can you advice me what direction should I go?
I'm not sure if this is what you're looking for but you can treat the DialogViewModel like any other screen. In this example Ok and Cancel get bound to the respective methods. Technically you could set x:Name="TryClose" for the cancel button's name but I named it Cancel for this example.
In the Open method in ShellViewModel you can preset values on the dialog before you display it. And after the result is returned since you have a reference to it you can also read those values.
Open method in ShellViewModel:
public void Open()
{
var dialogViewModel = IoC.Get<DialogViewModel>();
dialogViewModel.Dimension1 = "123";
dialogViewModel.Dimension2 = "456";
var result = WindowManager.ShowDialog(dialogViewModel);
if (dialogViewModel.MyDialogResult == DialogResult.OK) return;
//do stuff with results
var dim1 = dialogViewModel.Dimension1;
}
DialogView:
<Grid>
<StackPanel>
<TextBlock x:Name="Dimension1" />
<TextBlock x:Name="Dimension2" />
</StackPanel>
<StackPanel Height="50"
Orientation="Horizontal">
<Button x:Name="Ok"
Content="Ok" />
<Button x:Name="Cancel"
Content="cancel" />
</StackPanel>
</Grid>
DialogViewmodel:
[Export(typeof (DialogViewModel))]
public class DialogViewModel : Screen
{
private string _dimension1;
public string Dimension1
{
get { return _dimension1; }
set
{
_dimension1 = value;
NotifyOfPropertyChange(() => Dimension1);
}
}
private string _dimension2;
public string Dimension2
{
get { return _dimension2; }
set
{
_dimension2 = value;
NotifyOfPropertyChange(() => Dimension2);
}
}
public void Ok()
{
//Do stuff
MyDialogResult = DialogResult.OK;
TryClose();
}
public void Cancel()
{
MyDialogResult = DialogResult.Cancel;
TryClose();
}
}
If your ViewModel is based off of IScreen, use Close in the Ok or Cancel method. If you need to return a result, I'd suggest using the IEventAggregator to push the result back to the parent.
If you really need to, you can use GetView() to get the view associated with the viewmodel, cast it to the right type, and set the result (assuming the view has a result, I've not used the Dialog class).