I am working with WPF and the MVVM Light framework. In my project I have a GameViewModel and an associated view for it that acts as the main screen of my game (this is not my main ViewModel). Within the GameView, I would like to dynamically display other UserControls, triggered by button clicks or something of that nature, at a certain location within the grid. I'm not entirely sure how to go about this. I thought about using a secondary ViewModelLocator or something similar (which I wouldn't be sure how to go about doing at all), but before I dove into that I thought I'd ask here and see if there are any more practical ways to do this.
Say a button is clicked, UserControl1 is displayed at 3, 3 on the grid, then another button is clicked and UserControl2 is displayed at the same location. This is essentially what I'm trying to accomplish. While it is not imperative, I would like to use as much XAML and as little code-behind as possible, but anything that gets the job done in an MVVM-friendly manner will work perfectly fine for me. Any advice that you could give would be greatly appreciated. I'm guessing the solution is probably a lot easier than I'm making it out to be... it usually is.
One idea that I used on a recent project is to bind the Visibility property of the element to a Visibility property in the ViewModel. Then you can handle all of the displaying logic in the ViewModel.
Visibility="{Binding ElementVisibility}"
Then in the ViewModel you would have a property like
public const string ElementVisibilityPropertyname = "ElementVisibility";
private Visibility _elementVisibility = Visibility.Collapsed;
public Visibility ElementVisibility
{
get
{
return _elementVisibility;
}
set
{
if (_elementVisibility== value)
{
return;
}
RaisePropertyChanging(ElementVisibilityPropertyname );
_elementVisibility= value;
RaisePropertyChanged(ElementVisibilityPropertyname );
}
}
In your Button you would bind the Command property like so:
Command="{Binding ShowElement}"
And then in the ViewModel you would provide a RelayCommand
private RelayCommand _showElement;
public RelayCommand ShowElement
{
get
{
return _showElement?? (_showElement= new RelayCommand(() =>
{
this.ElementVisibility = Visibility.Visible;
));
}
}
Hopefully this gets you in the direction you are looking for!
I made a blog post about something quite similair here;
http://pjgcreations.blogspot.co.uk/2013/03/wpf-programmatically-adding-buttons.html
The following uses a Canvas, an ItemPresenter and a DataTemplate to show buttons populated from the ViewModel at runtime.
XAML:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Canvas x:Name="MyCanvas">
<ItemsControl ItemsSource="{Binding MyButtons}" Height="237" Width="507">
<ItemsControl.ItemsPanel >
<ItemsPanelTemplate>
<Canvas IsItemsHost="true"></Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Margin="{Binding ControlMargin}" Content="{Binding Content}" Command="{Binding DataContext.ButtonCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" CommandParameter="{Binding ProductId}"></Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Canvas>
</Window>
A Fluid Button Class;
Public Class FluidButton
Public Property Content As String
Public Property LeftPos As Double
Public Property TopPos As Double
Public Property ProductId As Double
'Returns the Control Margin, using the Class Properties
Public ReadOnly Property ControlMargin As Thickness
Get
Return New Thickness With {.Left = LeftPos, .Top = TopPos}
End Get
End Property
End Class
Properties in Your ViewModel;
'Our Collection of Buttons or Products
Public Property MyButtons As ObservableCollection(Of FluidButton)
'Used to expose the Button Pressed Execute Commands to the UI for Binding
Public Property ButtonCommand As DelegateCommand
To Add some Buttons;
MyButtons = New ObservableCollection(Of FluidButton)
MyButtons.Add(New FluidButton With {.Content = "Test1", .LeftPos = 0, .TopPos = 20, .ProductId = 1})
MyButtons.Add(New FluidButton With {.Content = "Test2", .LeftPos = 40, .TopPos = 30, .ProductId = 2})
MyButtons.Add(New FluidButton With {.Content = "Test3", .LeftPos = 80, .TopPos = 40, .ProductId = 3})
MyButtons.Add(New FluidButton With {.Content = "Test4", .LeftPos = 120, .TopPos = 50, .ProductId = 4})
MyButtons.Add(New FluidButton With {.Content = "Test5", .LeftPos = 160, .TopPos = 60, .ProductId = 5})
I'm sure you could easily modify this to suit your needs
Related
I have an AvalonDock (version 3.5) in my MVVM WPF application. The binding is as follows:
<xcad:DockingManager Name="_dockingManager" Grid.Row="1"
DataContext="{Binding DockingManagerViewModel}"
DocumentsSource="{Binding Documents}"
AnchorablesSource="{Binding Anchorables}" >
So when I add a new Anchorable to the corresponding "Anchorables" collection in my view model the corresponding view shows up. Nevertheless the views always show up docked on the right side of my application. How can I control that the view is docked on the left side of my application via the view model?
I don't think you can control this in you viewmodel.
There 2 ways for controlling this.
You can restore the layout from a previously saved (default) layout whenever the
application is started for te first time or
You can setup the XAML to use an initial layout as you wish (prefered solution)
For the second option:
You can use the XAML binding in the DockingManager class to implement your requirement:
See TestApp sample for full implementation of the below snippet (just change LeftSide to BottomSide to see the effect):
<avalonDock:DockingManager Grid.Row="1">
...
<avalonDock:LayoutRoot.LeftSide>
<avalonDock:LayoutAnchorSide>
<avalonDock:LayoutAnchorGroup>
<avalonDock:LayoutAnchorable Title="AutoHide1 Content" ContentId="AutoHide1Content" IconSource="/AvalonDock.TestApp;component/Images/address-book--pencil.png" >
<TextBox Text="{Binding TestTimer, Mode=OneWay, StringFormat='AutoHide Attached to Timer ->\{0\}'}"/>
</avalonDock:LayoutAnchorable>
<avalonDock:LayoutAnchorable Title="AutoHide2 Content" ContentId="AutoHide2Content">
<StackPanel Orientation="Vertical">
<TextBox/>
<TextBox/>
</StackPanel>
</avalonDock:LayoutAnchorable>
</avalonDock:LayoutAnchorGroup>
</avalonDock:LayoutAnchorSide>
</avalonDock:LayoutRoot.LeftSide>
</avalonDock:LayoutRoot>
</avalonDock:DockingManager>
You can add a property (call it InitialPosition, or something like that) to your Anchorable view model, and implement a ILayoutUpdateStrategy to position the anchorable on the left, right or bottom side.
Add something like this to your XAML:
<xcad:DockingManager …>
…
<xcad:DockingManager.LayoutUpdateStrategy>
<local:LayoutUpdate />
</xcad:DockingManager.LayoutUpdateStrategy>
</xcad:DockingManager>
and your LayoutUpdate class:
class LayoutUpdate: ILayoutUpdateStrategy
{
static Dictionary<PaneLocation, string> _paneNames = new Dictionary<PaneLocation, string>
{
{ PaneLocation.Left, "LeftPane" },
{ PaneLocation.Right, "RightPane" },
{ PaneLocation.Bottom, "BottomPane" },
};
public bool BeforeInsertAnchorable(LayoutRoot layout, LayoutAnchorable anchorableToShow, ILayoutContainer destinationContainer)
{
if (anchorableToShow.Content is IAnchorable anch)
{
var initLocation = anch.InitialLocation;
string paneName = _paneNames[initLocation];
var anchPane = layout.Descendents()
.OfType<LayoutAnchorablePane>()
.FirstOrDefault(d => d.Name == paneName);
if (anchPane == null)
{
anchPane = CreateAnchorablePane(layout, Orientation.Horizontal, initLocation);
}
anchPane.Children.Add(anchorableToShow);
return true;
}
return false;
}
static LayoutAnchorablePane CreateAnchorablePane(LayoutRoot layout, Orientation orientation,
PaneLocation initLocation)
{
var parent = layout.Descendents().OfType<LayoutPanel>().First(d => d.Orientation == orientation);
string paneName = _paneNames[initLocation];
var toolsPane = new LayoutAnchorablePane { Name = paneName };
if (initLocation == PaneLocation.Left)
parent.InsertChildAt(0, toolsPane);
else
parent.Children.Add(toolsPane);
return toolsPane;
}
public void AfterInsertAnchorable(LayoutRoot layout, LayoutAnchorable anchorable)
{
// here set the initial dimensions (DockWidth or DockHeight, depending on location) of your anchorable
}
This code is extracted and changed a bit from the working application, with different types and names. It should probably work, but there might be a typo or other error somewhere.
As I was required to sort of mask input in a textbox, I decided to construct my own control to handle this.
One of many templates could be "Size {enter size} Colour {enter colour}" which I've broken down to create a series of controls. The custom control that extends StackPanel which I've named CustomTextBox generates the following from the constructor.
// Pseudo
Children = {
Label = { Content = "Size" },
TextBox = { Text = "enter size" },
Label = { Content = "Colour" },
TextBox = { Text = "enter colour" }
// .. and an arbitrary amount of more Labels and TextBoxes in no particular order
}
So far so good. But when I want it to render.. That's where my headache starts.
I've tried to add the controls to the Children property and Measure/Arrange on the parent, itself and all the Children. ActualHeight and ActualWidth do change to something other than 0, but they won't render/display/become visible whatsoever.
I've also tried to use an ItemsControl and add the controls to the ItemsSource property to no avail.
I've tried to predefine sizes on everything, colour the background red and all, but the elusive controls remain to be caught and tied to my screen.
There's got to be a huge "Oooh..." here that I just can't find. I refuse to believe that this can't be done. I mean, it's WPF. WPF is awesome.
Edit Updated to what I currently have that seems most likely to work - still doesn't though.
Whatever I do in the designer shows up, but nothing I do in the CustomTextBox makes any visible difference.
Edit
New headline that fits the problem better.
Also, I've found several examples of programmatically adding controls. Take this article for example. I fail to see the difference between my scenario and theirs, except that theirs work and the buttons are visible.
Update3
The mistake was to assume, that one can simply replace control in visual tree by assigning in codebehind a new control to it's name (specified in xaml)
Updated2
Your mistake was following. If you write
<TextBlock Name="tb" Text="tb"/>
and then in code you will do
tb = new TextBlock() { Text = "Test" };
then you will have a new textblock as a variable, and nothing in xaml will change. You either have to change existing control, or remove old control and add new.
I'm talking about your Headline, Subtext & Description. You don't change them
Updated:
Here is an example of dynamically creating controls by specifying input mask:
MainWindow.xaml
<Window x:Class="WpfApplication35.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" xmlns:local="clr-namespace:WpfApplication35">
<Grid>
<local:UserControl1 x:Name="myUserControl"/>
</Grid>
</Window>
MainWindow.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
myUserControl.BuildControls("a {enter a} b {enter b1}{enter c2}");
}
}
UserControl1.xaml
<UserControl x:Class="WpfApplication35.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="30" d:DesignWidth="300">
<WrapPanel Name="root" Orientation="Horizontal"/>
</UserControl>
UserControl1.cs
public partial class UserControl1 : UserControl
{
public List<CustomField> Fields = new List<CustomField>();
public UserControl1()
{
InitializeComponent();
}
public UserControl1(string mask)
{
InitializeComponent();
BuildControls(mask);
}
public void BuildControls(string mask)
{
//Parsing Input
var fields = Regex.Split(mask, #"(.*?\}\s)");
foreach (var item in fields)
{
if (item != "")
{
int index = item.IndexOf('{');
string namestring = item.Substring(0, index).Trim();
var field = new CustomField() { Name = namestring };
string valuesstring = item.Substring(index, item.Length - index).Trim();
var values = valuesstring.Split(new char[] { '{', '}' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var val in values)
{
var valuewrapper = new FieldValue() { Value = val };
field.Values.Add(valuewrapper);
}
Fields.Add(field);
}
}
foreach (var field in Fields)
{
var stackPanel = new StackPanel() { Orientation = Orientation.Horizontal };
var label = new Label() { Content = field.Name, Margin = new Thickness(4) };
stackPanel.Children.Add(label);
foreach (var item in field.Values)
{
var tb = new TextBox() { Margin = new Thickness(4), Width = 200 };
tb.SetBinding(TextBox.TextProperty, new Binding() { Path = new PropertyPath("Value"), Source = item, Mode = BindingMode.TwoWay });
stackPanel.Children.Add(tb);
}
root.Children.Add(stackPanel);
}
}
}
public class CustomField
{
public string Name { get; set; }
public List<FieldValue> Values = new List<FieldValue>();
}
public class FieldValue
{
public string Value { get; set; }
}
This way fields and values are gonna be represented by Fields collection in UserControl1. Values of fields are updated as user types something. But only one-way, i.e. user input updates corresponding Value property, but changing Value property at runtime will not affect corresponding textbox. To implement updating from Value to textbox you have to implement INotifyProperty interface
Obsolete
Since you've asked.
There are hundreds of possible implementations, depending on what are you trying to archieve, how do you want validation to be, do you want to use MVVM, do you want to use bindings etc. There are generally 2 approaches : creating usercontrol and creating custom control. First one suits you better I believe.
Create a usercontrol with following xaml:
<Grid Height="24">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label Content="Size: " Grid.Column="0"/>
<TextBox Name="tbSize" Grid.Column="1"/>
<Label Content="Colour:" Grid.Column="2"/>
<TextBox Name="tbColour" Grid.Column="3"/>
</Grid>
In code-behind you can access TextBoxes by their name and do whatever you want to do.
You can use usercontrol in both xaml and codebehind.
In xaml:
Specify alias for namespace of your usercontrol (look at xmlns:local)
<Window x:Class="WpfApplication35.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
xmlns:local="clr-namespace:WpfApplication35">
<Grid>
<local:UserControl1/>
</Grid>
</Window>
In codebehind you can use it like this:
public MainWindow()
{
InitializeComponent();
var myUserControl = new UserControl1();
}
There is a lot to say and these are basic things, so check tutorials and ask questions.
P.S. If you are learning WPF it's mandatory to learn bindings.
I am creating an application in WPF using the MVVM approach. I am fairly new to the topic and I'm looking on pointers how to achieve the following: I have created a canvas in XAML, as follows:
<canvas name = "myCanvas">
...
</canvas>
I added a button, also in XAML, which I want to use to draw (programmatically) some basic shapes (line, rectangle, etc.) onto the existing canvas on mouse click. Since I'm using the MVVM approach, the button's command has to be bound to a method, as follows:
<Button name="myButton" Command="{Binding myMethod, Mode=OneWay}">
...
</Button>
I got the binding itself to work fine and I created the drawings and shapes in C#, but I don't understand how can I put the shapes onto the canvas that was created in XAML.
How can I address the canvas pre-made in XAML from my method? How do I go about this?
Edit:
The point of this is that I want to generate shapes based on data to visualise it. So, if my input has, say, 3 elements of type A, I want to create 3 rectangles and display them on the canvas. Later I want to make them clickable and display some information about them on click. MVVM is a set requirement for me.
In your view model you should have a representation of the shapes that does not use any UI elements:
public class ShapeItem
{
public Geometry Geometry { get; set; }
public Brush Fill { get; set; }
public Brush Stroke { get; set; }
}
public class ViewModel
{
public ObservableCollection<ShapeItem> ShapeItems { get; set; }
}
You would then use an ItemsControl with an appropriate ItemsPanel and ItemTemplate to visualize the shape items:
<ItemsControl ItemsSource="{Binding ShapeItems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Path Data="{Binding Geometry}"
Fill="{Binding Fill}"
Stroke="{Binding Stroke}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The view model might be initialized like shown below, and items may be added similarly when an appropriate command method is executed:
var vm = new ViewModel();
vm.ShapeItems = new ObservableCollection<ShapeItem>();
vm.ShapeItems.Add(
new ShapeItem
{
Geometry = new EllipseGeometry(new Point(100, 100), 100, 50),
Fill = Brushes.LightBlue
});
vm.ShapeItems.Add(
new ShapeItem
{
Geometry = new RectangleGeometry(new Rect(150, 100, 200, 100)),
Fill = Brushes.Azure,
Stroke = Brushes.Black
});
DataContext = vm;
Oxyplot graphs 13 points which are derived from the 6 user input text boxes. The values in the text boxes are held in public variables in the MainWindow.xaml.cs class. The variables are updated when the user presses enter in the text box. How would I make the refresh button refresh the graph.
private void RefreshButton_Click(object sender, RoutedEventArgs e)
{
//Refresh The Graph
}
I think that this would be done using the
PlotModel.RefreshPlot()
method, but I am not sure how to implement it because of Oxyplot's poor documentation.
I just updated to a new version of OxyPlot via NuGet. I'm using OxyPlot.Wpf v20014.1.277.1 and I think you now need to call InvalidatePlot(bool updateData) on the PlotModel instead of RefreshPlot (which is no longer available). I tested this in my sample code and it worked as expected.
If you want to refresh the plot and update the data collections, you need to pass true to the call:
PlotModel.InvalidatePlot(true)
Give x:Name to OxyPlot instance in XAML:
<oxy:Plot x:Name="Plot1"/>
and on button click handler, refresh like this:
private void RefreshButton_Click(object sender, RoutedEventArgs e)
{
Plot1.RefreshPlot(true);
}
The cleanest way I've found to get "sort of" auto-update is reacting to CollectionChanged on the collection that is LineSeries' ItemsSource.
In ViewModel:
ObservableCollection<DataPoint> Data { get; set; }
= new ObservableCollection<DataPoint>();
public PlotModel PlotModel
{
get { return _plot_model; }
set
{
_plot_model = value;
RaisePropertyChanged(() => PlotModel);
}
}
PlotModel _plot_model;
// Inside constructor:
Data.CollectionChanged += (a, b) => PlotModel.InvalidatePlot(true);
In the current OxyPlot.Wpf (1.0.0-unstable1983) you have two options:
Bind the Series.ItemsSource property from XAML to a collection in your viewmodel and exchange the whole collection, when you need an update. This also allows for concurrent async updates with larger data sets.
Bind the Plot.InvalidateFlag property of type int to your viewmodel and increment whenever you need an update. I haven't tested this approach, though.
The following code illustrates both options (pick one). XAML:
<oxy:Plot InvalidateFlag="{Binding InvalidateFlag}">
<oxy:Plot.Series>
<oxy:LineSeries ItemsSource="{Binding DataSeries}" />
</oxy:Plot.Series>
</oxy:Plot>
Updates on the ViewModel:
private async Task UpdateAsync()
{
// TODO do some heavy computation here
List<DataPoint> data = await ...
// option 1: Trigger INotifyPropertyChanged on the ItemsSource.
// Concurrent access is ok here.
this.DataSeries = data; // switch data sets
// option 2: Update the data in place and trigger via flag
// Only one update at a time.
this.DataSeries.Clear();
data.ForEach(this.DataSeries.Add);
this.InvalidateFlag++;
}
After having the same question with the same issue, it would seem that the only working solution (at least to my point of view) is as followed :
PlotView.InvalidatePlot(true)
Doing so, after updating one or multple Series do refresh your PlotView.
The refresh rate depends on how often, or at which rate your serie(s) is/are updated.
Here is a code snippet (on Xamarin Android but should work anyway) :
PlotView resultsChart = FindViewById<PlotView>(Resource.Id.resultsChart);
PlotModel plotModel = new PlotModel
{
// set here main properties such as the legend, the title, etc. example :
Title = "My Awesome Real-Time Updated Chart",
TitleHorizontalAlignment = TitleHorizontalAlignment.CenteredWithinPlotArea,
LegendTitle = "I am a Legend",
LegendOrientation = LegendOrientation.Horizontal,
LegendPlacement = LegendPlacement.Inside,
LegendPosition = LegendPosition.TopRight
// there are many other properties you can set here
}
// now let's define X and Y axis for the plot model
LinearAxis xAxis = new LinearAxis();
xAxis.Position = AxisPosition.Bottom;
xAxis.Title = "Time (hours)";
LinearAxis yAxis = new LinearAxis();
yAxis.Position = AxisPosition.Left;
yAxis.Title = "Values";
plotModel.Axes.Add(xAxis);
plotModel.Axes.Add(yAxis);
// Finally let's define a LineSerie
LineSeries lineSerie = new LineSeries
{
StrokeThickness = 2,
CanTrackerInterpolatePoints = false,
Title = "Value",
Smooth = false
};
plotModel.Series.Add(lineSerie);
resultsChart.Model = plotModel;
Now, whenever you need to add DataPoints to your LineSerie and to updated automatically the PlotView accordingly, just do as followed :
resultsChart.InvalidatePlot(true);
Doing so will automatically refresh your PlotView.
On a side note, the PlotView will also be updated when an event occurs such as a touch, a pinch to zoom, or any kind of UI-related events.
I hope I could help. I had trouble with this for a very long time.
Exists three alternatives how refresh plot (from OxyPlot documentation):
Change the Model property of the PlotView control
Call Invalidate on the PlotView control
Call Invalidate on the PlotModel
Another two years later... this solution works for me, because I have no oxyplot models and I´m missing some of the named functions from above.
code behind:
public partial class LineChart : UserControl
{
public LineChart()
{
InitializeComponent();
DataContext = this;
myChart.Title = "hier könnte Ihr Text stehen!";
this.Points = new List<DataPoint>();
randomPoints();
}
public IList<DataPoint> Points { get; private set; }
public void randomPoints()
{
Random rd = new Random();
String myText = "";
int anz = rd.Next(30, 60);
for (int i = 0; i < anz; i++)
myText += i + "," + rd.Next(0, 99) + ";";
myText = myText.Substring(0, myText.Length - 1);
String[] splitText = myText.Split(';');
for (int i = 0; i < splitText.Length; i++)
{
String[] tmp = splitText[i].Split(',');
Points.Add(new DataPoint(Double.Parse(tmp[0].Trim()), Double.Parse(tmp[1].Trim())));
}
while (Points.Count > anz)
Points.RemoveAt(0);
myChart.InvalidatePlot(true);
}
}
To update your data don't exchange the whole IList, rather add some new DataPoints to it and remove old ones at position 0.
XAML:
<UserControl x:Class="UxHMI.LineChart"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:UxHMI"
xmlns:oxy="http://oxyplot.org/wpf"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid x:Name="Container" Background="White">
<oxy:Plot x:Name="myChart" Title="{Binding Title}" FontFamily="Bosch Sans Medium" Foreground="#FF0C6596" FontSize="19" Canvas.Left="298" Canvas.Top="32" Background="AliceBlue" Margin="0,0,10,0">
<oxy:Plot.Series>
<oxy:LineSeries x:Name="ls" Background="White" ItemsSource="{Binding Points}" LineStyle="Solid" Color="ForestGreen" MarkerType="None" MarkerSize="5" MarkerFill="Black">
</oxy:LineSeries>
</oxy:Plot.Series>
</oxy:Plot>
<Button x:Name="button" Content="Random" HorizontalAlignment="Left" Margin="0,278,0,0" VerticalAlignment="Top" Width="75" Click="button_Click"/>
</Grid>
important are the x:Name="myChart" and ItemsSource="{Binding Points}"
I hope this is useful for someone out there
I want to create some sort of filter, when user clicks the filter button from the app bar it will fire up a popup page with list picker in it. I've googled and tried quite a number of solutions but still cannot get it to work.
Here are my codes:
XAML (MainPageView.xaml)
<phone:PhoneApplicationPage.Resources>
<DataTemplate x:Key="PivotContentTemplate">
<phone:Pivot Margin="-12,0,0,0" Title="FOREX NEWS" Height="672">
<phone:PivotItem Header="filter" FontFamily="{StaticResource PhoneFontFamilySemiLight}" FontSize="32">
<StackPanel Margin="12,0,0,0">
<toolkit:ListPicker Header="currencies" SelectionMode="Multiple"
micro:Message.Attach="[Event SelectionChanged] = [Action OnCurrenciesChanged($eventArgs)]">
<sys:String>gbp</sys:String>
<sys:String>eur</sys:String>
<sys:String>usd</sys:String>
</toolkit:ListPicker>
</StackPanel>
</phone:PivotItem>
</phone:Pivot>
</DataTemplate>
<phone:PhoneApplicationPage.Resources>
...
Still inside MainPageView.xaml
<bab:BindableAppBar Grid.Row="2" Mode="Minimized">
<bab:BindableAppBarButton micro:Message.Attach="[Event Click] = [Action ShowFilter($view, $eventArgs]">
</bab:BindableAppBarButton>
</bab:BindableAppBar>
MainPageViewModel.cs
public void ShowFilter(object sender, RoutedEventArgs e)
{
var view= sender as MainPageView;
CustomMessageBox messageBox = new CustomMessageBox()
{
ContentTemplate = (DataTemplate)view.Resources["PivotContentTemplate"],
LeftButtonContent = "filter",
RightButtonContent = "cancel",
IsFullScreen = true // Pivots should always be full-screen.
};
messageBox.Dismissed += (s1, e1) =>
{
switch (e1.Result)
{
case CustomMessageBoxResult.LeftButton:
// Do something.
break;
case CustomMessageBoxResult.RightButton:
// Do something.
break;
case CustomMessageBoxResult.None:
// Do something.
break;
default:
break;
}
};
messageBox.Show();
}
public void OnCurrenciesChanged(SelectionChangedEventArgs e)
{
}
For your information, I am using Caliburn.Micro and WP Toolkit for the CustomMessageBox and ListPicker.
I received exception No target found for method OnCurrenciesChanged. I only receive the exception when I after I select few items in the list picker and click any of the buttons to save the change. Another thing is that the OnCurrenciesChanged does not get triggered at all.
I think (based on what I read so far) whenever the CustomMessageBox get called, the datacontext its operating at is no longer pointing to the MainPageViewModel thus it could not find the method. But I am not sure how to actually do this.
More details:
Exception happen after I click the left button (checkmark)
Updates
So far I have try the following:
<StackPanel Margin="12,0,0,0" DataContext="{Binding DataContext, RelativeSource={RelativeSource TemplatedParent}}"> //also tried with Self
and I also added this when I instantiate messageBox
var messageBox = new CustomMessageBox()
{
ContentTemplate = (DataTemplate)view.Resources["PivotContentTemplate"],
DataContext = view.DataContext, // added this
LeftButtonContent = "filter",
RightButtonContent = "cancel",
IsFullScreen = true
};
The idea is that when the messsagebox is created, the datacontext will be the same as when the view is instantiated. However, it seems that the datacontext does not get inherited by the PickerList
Ok so I managed to get this to work. The solution is not pretty and I think it beats the purpose of MVVM at the first place.
Based on http://wp.qmatteoq.com/first-steps-in-caliburn-micro-with-windows-phone-8-how-to-manage-different-datacontext/ , inside a template the DataContext will be different. So, I need to somehow tell ListPicker to use the correct DataContext.
The solution provided by link above doesn't work for me. I think it is because when ListPicker is called inside CustomMessageBox, MainPageViewModel is no longer available or it seems not to be able to find it as suggested by the exception. So as per above code example in the question, even if I set the correct DataContext to the CustomMessageBox, it does not get inherited somehow by the ListPicker.
Here is my solution:
var messageBox = new CustomMessageBox()
{
Name = "FilterCustomMessageBox", // added this
ContentTemplate = (DataTemplate)view.Resources["PivotContentTemplate"],
DataContext = view.DataContext,
LeftButtonContent = "filter",
RightButtonContent = "cancel",
IsFullScreen = true
};
In the XAML, I edited to this
<toolkit:ListPicker Header="currencies" SelectionMode="Multiple"
micro:Action.TargetWithoutContext="{Binding ElementName=FilterCustomMessageBox, Path=DataContext}"
micro:Message.Attach="[Event SelectionChanged] = [Action OnCurrenciesChanged($eventArgs)]">
It's ugly because both ViewModel and View need to explicitly know the Name. In WPF, you can just do something like this in the binding to inherit the DataContext of the parent/etc but this is not available for WP.
{Binding DataContext, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGrid}}}
If anyone has better workaround, do let me know!