I want to drow so many graphs with OxyPlot Library.
And I'm testing now that I can add how many graphs with dynamic (but I'll not add graphs over 10000 graphs because it's max graphs in real application).
However when I'll add over 200 graphs, the Oxyplot throw exception.
Exception message is "This PlotModel is already in use by some other PlotView control.".
There are all of my code. (In code, I add graph with dynamic and I add values to the all graphs each 5 second in other thread.)
//Xaml -- MainView
<Window x:Class="OxyplotStressTest.MainWindow"
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:oxy="clr-namespace:OxyPlot.Wpf;assembly=OxyPlot.Wpf"
xmlns:local="clr-namespace:OxyplotStressTest"
mc:Ignorable="d"
Title="MainWindow" Height="550" Width="725">
<Grid>
<Button Content="Add 100" HorizontalAlignment="Left" Height="32" Margin="20,23,0,0" VerticalAlignment="Top" Width="65" Click="Button_Click100"/>
<Button Content="Add 50" HorizontalAlignment="Left" Height="32" Margin="102,23,0,0" VerticalAlignment="Top" Width="65" Click="Button_Click50"/>
<Button Content="Add 10" HorizontalAlignment="Left" Height="32" Margin="189,23,0,0" VerticalAlignment="Top" Width="65" Click="Button_Click10"/>
<Button Content="Add 1" HorizontalAlignment="Left" Height="32" Margin="274,23,0,0" VerticalAlignment="Top" Width="65" Click="Button_Click1"/>
<Grid Margin="20,65,19,10">
<ScrollViewer>
<ItemsControl ItemsSource="{Binding List}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<Label Content="{Binding Tag}"/>
<oxy:PlotView HorizontalAlignment="Left" Height="130" Margin="20,23,0,0" VerticalAlignment="Top" Width="600"
Model="{Binding Chart}" IsMouseWheelEnabled="False" IsManipulationEnabled="False" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
</Grid>
//C# -- MainWindow.cs
public partial class MainWindow : Window
{
private MainWindowViewModel mMainWindowViewModel = null;
public MainWindow()
{
InitializeComponent();
MainWindowViewModel mainWindowViewModel = new MainWindowViewModel();
this.DataContext = mainWindowViewModel;
mMainWindowViewModel = mainWindowViewModel;
Thread thread = new Thread(new ParameterizedThreadStart(reloadGraph));
thread.Start();
}
private void Button_Click100(object sender, RoutedEventArgs e)
{
AddGraph addGraph = new AddGraph(mMainWindowViewModel);
addGraph.addGraph(100);
}
private void Button_Click50(object sender, RoutedEventArgs e)
{
AddGraph addGraph = new AddGraph(mMainWindowViewModel);
addGraph.addGraph(50);
}
private void Button_Click10(object sender, RoutedEventArgs e)
{
AddGraph addGraph = new AddGraph(mMainWindowViewModel);
addGraph.addGraph(10);
}
private void Button_Click1(object sender, RoutedEventArgs e)
{
AddGraph addGraph = new AddGraph(mMainWindowViewModel);
addGraph.addGraph(1);
}
private void reloadGraph(object param)
{
while (true)
{
Thread.Sleep(5000);
if (mMainWindowViewModel.List.Count > 0)
{
AddGraph addGraph = new AddGraph(mMainWindowViewModel);
addGraph.reloadGraph();
}
Console.WriteLine(Environment.WorkingSet.ToString());
}
}
}
//C# -- MainViewModel
public MainWindowViewModel()
{
listVal = new ObservableCollection<Items>();
BindingOperations.EnableCollectionSynchronization(this.listVal, new object());
}
private ObservableCollection<Items> listVal = new ObservableCollection<Items>();
public ObservableCollection<Items> List
{
get
{
return listVal;
}
set
{
listVal = value;
NotifyPropertyChanged("List");
}
}
public class Items
{
public string Tag { get; set; }
private PlotModel chartVal = new PlotModel();
public PlotModel Chart
{
get
{
return chartVal;
}
set
{
chartVal = value;
}
}
}
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
//C# -- Model Class
class AddGraph
{
private MainWindowViewModel mMainWindowViewModel = null;
public AddGraph(MainWindowViewModel pMainWindowViewModel)
{
mMainWindowViewModel = pMainWindowViewModel;
}
public void addGraph(int pCount)
{
try
{
for (int i = 0; i < pCount; i++)
{
Random random = new Random();
long data = random.Next(100);
ColumnSeries column = new ColumnSeries();
column.FillColor = OxyColors.SkyBlue;
column.Items.Add(new ColumnItem() { Value = data });
data *= random.Next(50);
column.Items.Add(new ColumnItem() { Value = data });
Items items = new Items();
items.Tag = mMainWindowViewModel.List.Count.ToString();
items.Chart.Series.Add(column);
items.Chart.InvalidatePlot(true);
mMainWindowViewModel.List.Add(items);
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
public void reloadGraph()
{
try
{
List<Items> newList = new List<Items>();
foreach (Items listItem in mMainWindowViewModel.List)
{
string tag = listItem.Tag;
PlotModel plotModel = listItem.Chart;
ColumnSeries oldGraph = (ColumnSeries)plotModel.Series[0];
ColumnSeries newGraph = new ColumnSeries();
newGraph.FillColor = OxyColors.SkyBlue;
for (int i = 0; i < oldGraph.Items.Count; i++)
{
if (oldGraph.Items.Count == 30 && i == 0)
{
continue;
}
newGraph.Items.Add(new ColumnItem() { Value = oldGraph.Items[i].Value });
}
Random random = new Random();
long val = random.Next(500);
val *= random.Next(50);
newGraph.Items.Add(new ColumnItem() { Value = val });
Items items = new Items();
items.Tag = tag;
items.Chart.Series.Add(newGraph);
items.Chart.InvalidatePlot(true);
newList.Add(items);
}
#region //there are my solution and it shows good performance
int index = 0;
foreach (var addItem in newList)
{
mMainWindowViewModel.List[index] = addItem;
index++;
}
#endregion
#region //there are my first code and it has problem
mMainWindowViewModel.List.Clear();
foreach (var addItem in newList)
{
mMainWindowViewModel.List.Add(addItem);
}
#endregion
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
}
Maybe my code is bad, but I don't know where is the bad code because it dosen't stop on BreakPoint.
If you give me a advice, I'm so glad.
I think you may need to rebuild it a little using idea from OxyPlot Example: https://github.com/oxyplot/oxyplot/tree/develop/Source/Examples/WPF/WpfExamples
When i were working last months with oxyplots i found out that this official examples are well done with they ideas, they solution with Performance Demo using this virtualization may helps with performance:
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel IsVirtualizing="True" IsItemsHost="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
The problem may cause using NotifyPropertyChange as refreshing UI as adding new chart maybe you should somehow Unbind List > Notify > Bind New List.
Check more in the link and try to mash-ups they solution to make your own :)
I glad if i have helped some :)
Related
I have a UserControl which contains a InkToolbar. In this control I'm generating another set of UserControls based on user input. In each of those programmatically generated controls, contains a InkCanvas.
This is the parent UserControl & the code behind.
Main.xaml
<UserControl
x:Class="uwp.Main"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:uwp_mvvm"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Grid.Row="0">
<InkToolbar x:Name="InkToolbar" Background="Black" Margin="10 0 0 0" VerticalAlignment="Center"/>
</StackPanel>
<ScrollViewer ZoomMode="Enabled" Background="DarkGray" x:Name="ScrollViewer" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" MinZoomFactor="0.25" Width="Auto" Height="Auto" MaxZoomFactor="4" Grid.Row="1">
<StackPanel Orientation="Vertical" VerticalAlignment="Center" HorizontalAlignment="Center">
<ItemsControl x:Name="PagesItemsControl" ItemsSource="{x:Bind Pages, Mode=OneWay}" HorizontalAlignment="Center" VerticalAlignment="Top">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapGrid Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:TestControl x:Name="TestControl" Page="{Binding}" Margin="4 4" InkToolbarControl="{Binding Path=InkToolbar, ElementName=InkToolbar}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ScrollViewer>
</Grid>
</UserControl>
Main.xaml.cs
public sealed partial class MainViewer : UserControl
{
public MainViewer()
{
this.InitializeComponent();
}
private void EventTriggerChanged(object sender, PropertyChangedEventArgs e)
{
var trigger = sender as EventTriggerTestControl;
if (trigger != null)
RaiseChanged(e.PropertyName);
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaiseChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propName));
}
private PdfDocument pdfDocument = null;
private string password = string.Empty;
private ObservableCollection<PdfPage> pages = new ObservableCollection<PdfPage>();
public string Password
{
private get { return password; }
set { if (value != password) { password = value; } }
}
public ObservableCollection<PdfPage> Pages
{
get { return pages; }
set { if (value != pages) { pages = value; } }
}
public StorageFile File
{
get { return (StorageFile)GetValue(FileProperty); }
set { SetValue(FileProperty, value); }
}
public static readonly DependencyProperty FileProperty =
DependencyProperty.Register(nameof(File), typeof(StorageFile), typeof(MainViewer), new PropertyMetadata(null, new PropertyChangedCallback(OnDocumentChanged)));
private static void OnDocumentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (object.Equals(e.NewValue, e.OldValue) || e.NewValue is null)
return;
d.RegisterPropertyChangedCallback(FileProperty, CaptureDocument);
}
private async static void CaptureDocument(DependencyObject sender, DependencyProperty dp) => await (sender as MainViewer).OpenFileAsync(sender.GetValue(dp) as StorageFile);
private async Task OpenFileAsync(StorageFile file)
{
try
{
if (file == null)
throw new ArgumentNullException(nameof(file));
var files = await ApplicationData.Current.LocalFolder.GetFilesAsync(CommonFileQuery.OrderByName);
if (files.Where(x => x.Name == file.Name).ToList().Count == 0)
await file.CopyAsync(ApplicationData.Current.LocalFolder, file.Name, NameCollisionOption.ReplaceExisting);
file = await ApplicationData.Current.LocalFolder.GetFileAsync(file.Name);
pdfDocument = new PdfDocument(file.Path, Password);
pdfDocument.Pages.ToList().ForEach(x => Pages.Add(x));
}
catch (Exception ex) { throw ex; }
}
}
I want to use the InkToolbar which is in the parent control inside the TestControl
TestControl.xaml
<UserControl
x:Class="uwp.TestControl"
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="using:uwp_mvvm.ViewModels" xmlns:converter="using:uwp_mvvm"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<UserControl.Resources>
<converter:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
</UserControl.Resources>
<Grid x:Name="Viewport" VerticalAlignment="Top" HorizontalAlignment="Center">
<Border x:Name="ViewportBorder" Background="White" BorderThickness="2, 2, 2, 2" BorderBrush="#FF353334" />
<Image x:Name="Image" Margin="0" UseLayoutRounding="True" ManipulationMode="Scale"/>
<Canvas x:Name="SelectionCanvas" CompositeMode="MinBlend" Opacity="1" />
<InkCanvas x:Name="InkCanvas" />
</Grid>
</UserControl>
TastControl.xaml.cs
public sealed partial class TestControl : UserControl
{
private const float DPI = 256;
public PdfPage Page
{
get { return (PdfPage)GetValue(PageProperty); }
set { SetValue(PageProperty, value); }
}
public static readonly DependencyProperty PageProperty =
DependencyProperty.Register(nameof(Page), typeof(PdfPage), typeof(TestControl), new PropertyMetadata(null, new PropertyChangedCallback(OnPageChanged)));
private static void OnPageChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (object.Equals(e.NewValue, e.OldValue) || e.NewValue is null)
return;
d.RegisterPropertyChangedCallback(PageProperty, CapturePage);
}
private static void CapturePage(DependencyObject sender, DependencyProperty dp) => (sender as TestControl).RenderPage(sender.GetValue(dp) as PdfPage);
public InkToolbar InkToolbarControl
{
get { return (InkToolbar)GetValue(InkToolbarControlProperty); }
set { SetValue(InkToolbarControlProperty, value); }
}
public static readonly DependencyProperty InkToolbarControlProperty =
DependencyProperty.Register(nameof(InkToolbarControl), typeof(InkToolbar), typeof(TestControl), new PropertyMetadata(null));
public TestControl()
{
this.InitializeComponent();
ConfigureInkCanvas();
}
private void ConfigureInkCanvas()
{
InkCanvas.InkPresenter.InputDeviceTypes = CoreInputDeviceTypes.Mouse | CoreInputDeviceTypes.Pen | CoreInputDeviceTypes.Touch;
InkCanvas.InkPresenter.InputProcessingConfiguration.Mode = InkInputProcessingMode.None;
InkCanvas.InkPresenter.UpdateDefaultDrawingAttributes(
new InkDrawingAttributes
{
IgnorePressure = false,
FitToCurve = true,
Color = Colors.Black,
PenTip = PenTipShape.Circle
});
InkCanvas.InkPresenter.UnprocessedInput.PointerEntered -= InkCanvasUnprocessedInputPointerEntered;
InkCanvas.InkPresenter.UnprocessedInput.PointerEntered += InkCanvasUnprocessedInputPointerEntered;
InkCanvas.InkPresenter.UnprocessedInput.PointerExited -= InkCanvasUnprocessedInputPointerExited;
InkCanvas.InkPresenter.UnprocessedInput.PointerExited += InkCanvasUnprocessedInputPointerExited;
}
private void InkCanvasUnprocessedInputPointerExited(InkUnprocessedInput sender, PointerEventArgs args)
{
//InkCanvas.InkPresenter.InputProcessingConfiguration.Mode = InkInputProcessingMode.Inking;
//InkToolbarControl.TargetInkCanvas = InkCanvas;
}
private void InkCanvasUnprocessedInputPointerEntered(InkUnprocessedInput sender, PointerEventArgs args)
{
//InkCanvas.InkPresenter.InputProcessingConfiguration.Mode = InkInputProcessingMode.None;
//InkToolbarControl.TargetInkCanvas = null;
}
private void RenderPage(PdfPage page, float dpi = DPI)
{
if (page is null)
return;
this.DataContext = page;
var width = (DataContext as PdfPage).Width;
var height = (DataContext as PdfPage).Height;
var writableBitmap = new WriteableBitmap((int)(width / 72f * dpi), (int)(height / 72f * dpi));
(DataContext as PdfPage).Render(writableBitmap, PageOrientations.Normal, RenderingFlags.Annotations);
Image.Width = width;
Image.Height = height;
Image.Source = writableBitmap;
SelectionCanvas.Width = width;
SelectionCanvas.Height = height;
InkCanvas.Width = width;
InkCanvas.Height = height;
Canvas.SetZIndex(Image, 0);
ConfigureInkCanvas();
}
}
I tried to use a DependencyProperty to send the InkToolbar to the TestControl but it didn't seem to work.
Could someone please help me with this? I'm not sure whether it is possible to set the TargetInkCanvas like this also. So, if there is a better approach for this any suggestions are also appreciated.
Any help is much appreciated. Thanks.
Access InkToolbar placed in parent element from programmatically generated child elements
Above solution that use DependencyProperty to pass parent InkToolBar is correct. However you bind wrong value for InkToolbarControl property.
Please bind ElementName without path directly.
<local:TestControl
x:Name="TestControl"
Margin="4,4"
InkToolbarControl="{Binding ElementName=InkToolbar}" />
Then update InkCanvasUnprocessedInputPointerExited and InkCanvasUnprocessedInputPointerEntered process logic.
private void InkCanvasUnprocessedInputPointerExited(InkUnprocessedInput sender, PointerEventArgs args)
{
InkCanvas.InkPresenter.InputProcessingConfiguration.Mode = InkInputProcessingMode.Inking;
InkToolbarControl.TargetInkCanvas = null;
}
private void InkCanvasUnprocessedInputPointerEntered(InkUnprocessedInput sender, PointerEventArgs args)
{
InkCanvas.InkPresenter.InputProcessingConfiguration.Mode = InkInputProcessingMode.None;
InkToolbarControl.TargetInkCanvas = InkCanvas;
}
I cant get nice reorder animation in ListView - also tried overriding all the various internal transitions via the <Transitions><TransitionsCollection>... property. Nothing. Tried using simple .Insert/.Remove and .Move and neither works nicely.
Insert/Remove has animation for the items that happen to have been pushed around and .Move acts like a Reset on the collection.
The following code in MainPage:
public sealed partial class MainPage : Page
{
private ObservableCollection<Holder> Items = new ObservableCollection<Holder>(Enumerable.Range(0, 10).Select(x => new Holder(x)));
public MainPage()
{
this.InitializeComponent();
Items.CollectionChanged += Items_CollectionChanged;
}
private void Items_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
}
private Random _random = new Random();
private bool asc = true;
private void Button_Click(object sender, RoutedEventArgs e)
{
IList<Holder> temp;
//var temp = Items.Skip(1).Union(Items.Take(1)).ToList();
// var temp = new Holder[] { Items.ElementAt(Items.Count - 1) }.Union(Items.Take(Items.Count - 1)).ToList();
temp = Items.OrderBy(x => _random.NextDouble()).ToList();
// temp = asc ? Items.OrderBy(x => x.I).ToList() : Items.OrderByDescending(x => x.I).ToList();
asc = !asc;
for (int i = 0; i < temp.Count; i++)
{
if (Items[i] != temp[i])
{
// When using Move, no animation occurs at all. When using Remove/Insert, there's partial
// animation for the items that happened to stay at the same place.
//Items.Move(Items.IndexOf(temp[i]), i);
Items.Remove(temp[i]);
Items.Insert(i, new Holder(temp[i].I));
}
}
}
}
public class Holder
{
public Holder(int i)
{
I = i;
}
public string Name => "Hey hey hey " + I.ToString();
public int I { get; private set; }
public override bool Equals(object obj)
{
return ((Holder)obj).I == I;
}
public override int GetHashCode()
{
return I.GetHashCode();
}
}
With the following XAML:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Button Content="Mix" Click="Button_Click" Grid.Row="1"></Button>
<ListView ItemsSource="{x:Bind Items}" >
<ListView.ItemTemplate>
<DataTemplate>
<Border BorderThickness="3" BorderBrush="Blue">
<TextBlock Text="{Binding Name}" Margin="20"/>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
When I enter text into a Textbox, it updates two TextBlocks at the same time. I'm trying to retrieve that value to save it to a sql database. I've temporarily set it up to display the value in a MessageBox.
ViewModel/Model:
private decimal _amount;
public decimal Amount
{
get
{
return _amount;
}
set
{
_amount = value;
OnPropertyChanged("Amount");
}
}
TextBox Binding:
<TextBox MaxLength="7" Visibility="{Binding Hide1, Converter={StaticResource BoolToVis},FallbackValue=Visible}" Text="{Binding Amount, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Grid.Row="2" />
TextBlocks Binding:
<TextBlock Foreground="Black" Margin="0 0 0 0" VerticalAlignment="Top" Grid.Column="3" Text="{Binding Path=Amount}"/>
<TextBlock Grid.Column="3" Text="{Binding Amount}" Foreground="Black" Margin="0 0 0 0" VerticalAlignment="Bottom"/>
SaveCommand:
private async void Save()
{
try
{
MessageBox.Show(string.Format("{0}", Amount));
}
catch (DbEntityValidationException ex)
{
foreach (var en in ex.EntityValidationErrors)
{
var exceptionDialog = new MessageDialog
{
Message = { Text = string.Format("{0}, {1}", en.Entry.Entity.GetType().Name, en.Entry.State) }
};
await DialogHost.Show(exceptionDialog, "RootDialog");
foreach (var ve in en.ValidationErrors)
{
exceptionDialog = new MessageDialog
{
Message = { Text = string.Format("{0}, {1}", ve.PropertyName, ve.ErrorMessage) }
};
await DialogHost.Show(exceptionDialog, "RootDialog");
}
}
}
catch(Exception ex)
{
var exceptionDialog = new MessageDialog
{
Message = { Text = string.Format("{0}", ex) }
};
await DialogHost.Show(exceptionDialog, "RootDialog");
}
}
When I hit save, the MessageBox displays 0.
EDIT: I've just remembered that I have the ViewModel connected to two UserControls. My TabLayout, which handles the content of TabItems; and Payroll which contains the save button and a TabControl which loads TabLayout into each TabItem.
DataContext for both is:
public TabLayout()
{
InitializeComponent();
DataContext = new PayslipModel();
}
public Payroll()
{
InitializeComponent();
DataContext = new PayslipModel();
}
If you want both controls to share a DataContext then you can inject an instance from your composition root, i.e. App.Xaml.cs The following probably doesn't match the way your application is laid out but should give you the idea. Down the road you can look at dependency injection and how to compose your object graph but this a start.
App.xaml
<Application x:Class="App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Startup="OnAppStartup">
<Application.Resources>
</Application.Resources>
</Application>
App.xmal.cs
public partial class App : Application {
private void OnAppStartup(object sender, StartupEventArgs e) {
Application.Current.ShutdownMode = System.Windows.ShutdownMode.OnMainWindowClose;
var vm = new PayslipModel();
var mainWindow = new MainWindow(vm);
Application.Current.MainWindow = mainWindow;
mainWindow.Show();
}
}
MainWindow.xaml.cs
public MainWindow(PayslipModel vm){
InitializeComponent();
tabControl = new TabControl(vm);
payRoll = new PayRoll(vm);
}
UserControls
public TabLayout(PayslipModel vm)
{
InitializeComponent();
DataContext = vm;
}
public Payroll(PayslipModel vm)
{
InitializeComponent();
DataContext = vm;
}
I have structured the classes like this for my Universal app. Currently working in the WP8.1 part.
The following classes are put in the shared code. (Hoping to use it in Win8.1)
FolderItemViewer.xaml(UserControl) It is the DataTemplate for a ListView in the MainPage.xaml
FolderCollection class, which is the collection that is bound to the Listview in the Mainpage.xaml of the WP
Now the problem is, I have wired the manipulation events to the datatemplate grids in the FolderItemViewer.xaml to capture the right and left swipe and it is working. Now based on this, I need to update that CollectionItem in FolderCollection class and hence the ListView in Mainpage.xaml.
How do I capture that listview item or the collectionitem bound since the manipulation events lie in the FolderItemViewer class?
Can I get the listview item? Or a call back function to the listlivew item template changed? or somethin like that?
EDIT
Sorry to put in so much code. But really appreciate somebody's help in getting this right.
This is the FolderItemViewer.xaml
<UserControl
x:Class="JusWrite2.FolderItemViewer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:JusWrite2"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid x:Name="MainGrid">
<Grid Height="60" Width="380" Margin="0,0,0,1">
<Grid x:Name="ItemGrid" HorizontalAlignment="Left" VerticalAlignment="Center" Width="380" Height="60" Background="Transparent" Canvas.ZIndex="2"
ManipulationMode="TranslateX,System" ManipulationStarted="On_ChannelItem_ManipulationStarted" ManipulationDelta="On_ChannelItem_ManipulationDelta" ManipulationCompleted="OnChannelItemManipulationCompleted">
<TextBlock x:Name="titleTextBlock" Margin="20,0,0,0" Canvas.ZIndex="2" VerticalAlignment="Center" TextAlignment="Left" FontSize="25" >
</TextBlock>
</Grid>
<Grid x:Name="DelGrid" Opacity="0.0" HorizontalAlignment="Right" VerticalAlignment="Center" Height="60" Background="Red" Canvas.ZIndex="-1" Tapped="On_ChannelDelete_Tap" Width="380">
<Button Content="X" FontSize="25" Canvas.ZIndex="-1" VerticalAlignment="Center" HorizontalAlignment="Center" Width="380" BorderThickness="0" />
</Grid>
</Grid>
</Grid>
</UserControl>
code behind
private void OnChannelItemManipulationCompleted(object sender, ManipulationCompletedRoutedEventArgs e)
{
Grid ChannelGrid = (Grid)sender;
Grid mGrid = (Grid)(ChannelGrid.Parent);
Grid DeleteGrid = (Grid)((Grid)(ChannelGrid.Parent)).Children[1];
FolderCollection swipedItem = ChannelGrid.DataContext as FolderCollection;// grid has null value for datacontext
double dist = e.Cumulative.Translation.X;
if (dist < -100)
{
// Swipe left
}
else
{
// Swipe right
}
}
FolderCollection.xaml has two classes in it. FolderItem and FolderCollection
public class FolderItem : INotifyPropertyChanged
{
// variables
public FolderItem()
{
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public int CompletionStatus
{
//code
}
public int Priority
{
//code
}
public string FolderText
{
//code
}
public int PenColor
{
//code
}
public string UUID
{
//code
}
public string CreateUUID()
{
//code
}
}
public class FolderCollection : IEnumerable<Object>
{
private ObservableCollection<FolderItem> folderCollection = new ObservableCollection<FolderItem>();
private static readonly FolderCollection instance = new FolderCollection();
public static FolderCollection Instance
{
get
{
return instance;
}
}
public IEnumerator<Object> GetEnumerator()
{
return folderCollection.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Add(FolderItem fItem)
{
folderCollection.Add(fItem);
}
public ObservableCollection<FolderItem> FolderCollectionInstance
{
get
{
return folderCollection;
}
}
}
And this is the MainPage.xaml where I have data bound.
// Resources
<DataTemplate x:Key="StoreFrontTileTemplate">
<local:FolderItemViewer />
</DataTemplate>
<ListView x:Name="FolderListView" ItemsSource="{Binding}"
SelectionMode="None"
ItemTemplate="{StaticResource StoreFrontTileTemplate}"
ContainerContentChanging="ItemListView_ContainerContentChanging">
</ListView>
code behind
//Constructor
FolderListView.DataContext = fc.FolderCollectionInstance;
FolderListView.ItemsSource = fc.FolderCollectionInstance;
private void ItemListView_ContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args)
{
FolderItemViewer iv = args.ItemContainer.ContentTemplateRoot as FolderItemViewer;
if (args.InRecycleQueue == true)
{
iv.ClearData();
}
else if (args.Phase == 0)
{
iv.ShowPlaceholder(args.Item as FolderItem);
// Register for async callback to visualize Title asynchronously
args.RegisterUpdateCallback(ContainerContentChangingDelegate);
}
else if (args.Phase == 1)
{
iv.ShowTitle();
args.RegisterUpdateCallback(ContainerContentChangingDelegate);
}
else if (args.Phase == 2)
{
//iv.ShowCategory();
//iv.ShowImage();
}
// For imporved performance, set Handled to true since app is visualizing the data item
args.Handled = true;
}
private TypedEventHandler<ListViewBase, ContainerContentChangingEventArgs> ContainerContentChangingDelegate
{
get
{
if (_delegate == null)
{
_delegate = new TypedEventHandler<ListViewBase, ContainerContentChangingEventArgs>(ItemListView_ContainerContentChanging);
}
return _delegate;
}
}
private TypedEventHandler<ListViewBase, ContainerContentChangingEventArgs> _delegate;
The list item should be available as the data context of the grid: ChannelGrid.DataContext.
I am working in WPF -- There is button with click event handler in my application. As i click on button it's event handler generates a new row in grid named as grids. In this new Row i want to add another grid programmatically to add Label, Button and TextBox in this grid in row.
As i executed my code it only generates a texboxes! where labels and button shown once! Here code and image is : Please feel free to ask if my query is not clear to you!
int r =0;
private void button2_Click(object sender, RoutedEventArgs e)
{
TextEdit text1; Button button1; Grid grid1;
grids.RowDefinitions.Add(new RowDefinition());
text1 = new TextEdit();
text1.SetValue(Grid.ColumnProperty, 1);
text1.SetValue(Grid.RowProperty, r);
button1 = new Button();
button1.Content = "Left + " + r;
button1.Click += new RoutedEventHandler(button1_Click);
button1.SetValue(Grid.ColumnProperty, 1);
button1.SetValue(Grid.RowProperty, r);
grid1 = new Grid();
grid1.SetValue(Grid.ColumnProperty, 1);
grids.RowDefinitions.Add(new RowDefinition());
grid1.SetValue(Grid.RowProperty, r);
grids.Children.Add(button1);
grids.Children.Add(text1);
r = r + 1;
}
EDIT
int r =0;
private void button2_Click(object sender, RoutedEventArgs e)
{
db obj = new db();
var query = from p in obj.TableA select p ;
foreach(var a in query.ToList())
{
TextEdit text1; Button button1; Grid grid1;
grids.RowDefinitions.Add(new RowDefinition());
text1 = new TextEdit();
text1.SetValue(Grid.ColumnProperty, 1);
text1.SetValue(Grid.RowProperty, r);
button1 = new Button();
button1.Content = a.name;
button1.Click += new RoutedEventHandler(button1_Click);
button1.SetValue(Grid.ColumnProperty, 1);
button1.SetValue(Grid.RowProperty, r);
grid1 = new Grid();
grid1.SetValue(Grid.ColumnProperty, 1);
grids.RowDefinitions.Add(new RowDefinition());
grid1.SetValue(Grid.RowProperty, r);
grids.Children.Add(button1);
grids.Children.Add(text1);
r = r + 1;}
}
Ok. Delete all your code and start all over.
If you're working with WPF, you really need to have The WPF Mentality
As a general rule, you almost never create or manipulate UI elements in procedural code in WPF.
That's what XAML is for.
This the right way to do what you're asking in WPF (in a full working example):
<Window x:Class="MiscSamples.ItemsControlSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dxe="http://schemas.devexpress.com/winfx/2008/xaml/editors"
Title="ItemsControlSample" Height="300" Width="300">
<DockPanel>
<Button Content="Add New Row" Command="{Binding AddNewRowCommand}"
DockPanel.Dock="Bottom"/>
<ItemsControl ItemsSource="{Binding Data}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Black" Background="Gainsboro" BorderThickness="1" Margin="2">
<!-- This is the Inner Grid for each element, which is represented in Brown color in your picture -->
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width=".2*"/>
<ColumnDefinition Width=".2*"/>
</Grid.ColumnDefinitions>
<Label Content="{Binding Label1Text}"
Margin="2"/>
<Button Content="Button1"
Command="{Binding DataContext.Command1, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
CommandParameter="{Binding}"
Grid.Column="1" Margin="2"/>
<Button Content="Button2"
Command="{Binding DataContext.Command2, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
CommandParameter="{Binding}"
Grid.Column="2" Margin="2"/>
<dxe:TextEdit Text="{Binding Text}"
Grid.Row="1" Grid.ColumnSpan="3"
Margin="2"/>
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.Template>
<ControlTemplate TargetType="ItemsControl">
<ScrollViewer CanContentScroll="True">
<ItemsPresenter/>
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DockPanel>
</Window>
Code Behind:
public partial class ItemsControlSample : Window
{
public ItemsControlSample()
{
InitializeComponent();
DataContext = new ItemsControlSampleViewModel();
}
}
ViewModel:
public class ItemsControlSampleViewModel
{
public ObservableCollection<ItemsControlSampleData> Data { get; set; }
public Command AddNewRowCommand { get; set; }
public Command<ItemsControlSampleData> Command1 { get; set; }
public Command<ItemsControlSampleData> Command2 { get; set; }
public ItemsControlSampleViewModel()
{
var sampledata = Enumerable.Range(0, 10)
.Select(x => new ItemsControlSampleData()
{
Label1Text = "Label1 " + x.ToString(),
Text = "Text" + x.ToString()
});
Data = new ObservableCollection<ItemsControlSampleData>(sampledata);
AddNewRowCommand = new Command(AddNewRow);
Command1 = new Command<ItemsControlSampleData>(ExecuteCommand1);
Command2 = new Command<ItemsControlSampleData>(ExecuteCommand2);
}
private void AddNewRow()
{
Data.Add(new ItemsControlSampleData() {Label1Text = "Label 1 - New Row", Text = "New Row Text"});
}
private void ExecuteCommand1(ItemsControlSampleData data)
{
MessageBox.Show("Command1 - " + data.Label1Text);
}
private void ExecuteCommand2(ItemsControlSampleData data)
{
MessageBox.Show("Command2 - " + data.Label1Text);
}
}
Data Item:
public class ItemsControlSampleData
{
public string Label1Text { get; set; }
public string Text { get; set; }
}
Helper classes:
public class Command : ICommand
{
public Action Action { get; set; }
public string DisplayName { get; set; }
public void Execute(object parameter)
{
if (Action != null)
Action();
}
public bool CanExecute(object parameter)
{
return IsEnabled;
}
private bool _isEnabled = true;
public bool IsEnabled
{
get { return _isEnabled; }
set
{
_isEnabled = value;
if (CanExecuteChanged != null)
CanExecuteChanged(this, EventArgs.Empty);
}
}
public event EventHandler CanExecuteChanged;
public Command(Action action)
{
Action = action;
}
}
public class Command<T>: ICommand
{
public Action<T> Action { get; set; }
public void Execute(object parameter)
{
if (Action != null && parameter is T)
Action((T)parameter);
}
public bool CanExecute(object parameter)
{
return IsEnabled;
}
private bool _isEnabled = true;
public bool IsEnabled
{
get { return _isEnabled; }
set
{
_isEnabled = value;
if (CanExecuteChanged != null)
CanExecuteChanged(this, EventArgs.Empty);
}
}
public event EventHandler CanExecuteChanged;
public Command(Action<T> action)
{
Action = action;
}
}
Result:
Notice how I'm not dealing with UI in procedural code, but instead I'm using DataBinding and simple, simple properties. That's how you program in WPF. That's what the WPF mentality is about.
I'm using an ItemsControl and a DataTemplate defined in XAML to let WPF create the UI for each of my data items.
Also notice how my code does nothing except expose the data and define reusable Commands that serve as abstractions to the user actions such as Button clicks. This way you can concentrate in coding your business logic instead of struggling with how to make the UI work.
The buttons inside each item are bound to the Commands using a RelativeSource Binding to navigate upwards in the Visual Tree and find the DataContext of the ItemsControl, where the Commands are actually defined.
When you need to add a new item, you just add a new item to the ObservableCollection that contains your data and WPF automatically creates the new UI elements bound to that.
Though this might seem like "too much code", most of the code I posted here is highly reusable and can be implemented in a Generic ViewModel<T> that is then reusable for any type of data items. Command and Command<T> are also write-once reusable classes that can be found in any MVVM framework such as Prism, MVVM Light or Caliburn.Micro.
This approach is really much preferred in WPF, because it enables a great amount of scalability and independence between the UI and the business logic, and it also enables testability of the ViewModel.
I suggest you read all the materials linked in the post, most importantly Rachel's WPF Mentality and related blog posts. Let me know if you need further help.
WPF Rocks. Just copy and paste my code in a File -> New Project -> WPF Application and see the results for yourself.
It's actually much easier in behind code then in xaml code..
My Xaml code:
<Window x:Class="WpfAddGridWithStackPanel.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">
<Grid>
<Grid x:Name="Grid_Grid" Margin="0,0,0,32">
<Grid>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<Grid x:Name="Grid_Grid" Margin="0,0,0,32"/>
</ScrollViewer>
<Button x:Name="btn_Add" Height="32" DockPanel.Dock="Bottom" VerticalAlignment="Bottom" Content="Add New Row" Click="btn_Add_Click" Width="150" HorizontalAlignment="Left" UseLayoutRounding="True" />
<Button x:Name="btn_Remove" Height="32" DockPanel.Dock="Bottom" VerticalAlignment="Bottom" Content="Remove last Row" Click="btn_Remove_Click" Width="150" HorizontalAlignment="Right" />
</Grid>
</Window>
And Code behind:
public partial class MainWindow : Window
{
int num = 0;
public MainWindow()
{
InitializeComponent();
}
void btn1_Click(object sender, RoutedEventArgs e)
{
throw new NotImplementedException();
}
void btn2_Click(object sender, RoutedEventArgs e)
{
throw new NotImplementedException();
}
private void btn_Remove_Click(object sender, RoutedEventArgs e)
{
try
{
Grid_Grid.RowDefinitions.RemoveAt(Grid_Grid.RowDefinitions.Count - 1);
Grid_Grid.Children.RemoveAt(Grid_Grid.Children.Count - 1);
num--;
}
catch { }
}
private void btn_Add_Click(object sender, RoutedEventArgs e)
{
StackPanel stack = new StackPanel();
DockPanel dock = new DockPanel();
Label lbl = new Label();
Button btn1 = new Button();
Button btn2 = new Button();
TextBox txt1 = new TextBox();
stack.Children.Add(dock);
stack.Children.Add(txt1);
dock.Children.Add(lbl);
dock.Children.Add(btn2);
dock.Children.Add(btn1);
#region StackPanel Properties
stack.Background = Brushes.LightGray;
#endregion
#region DockPanel Content Properties
lbl.Content = "Label " + (num + 1).ToString();
lbl.Height = 32;
lbl.Width = 100;
lbl.FontSize = 12;
lbl.SetValue(DockPanel.DockProperty, Dock.Left);
lbl.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
btn1.Content = "Butten 1";
btn1.Height = 32;
btn1.Width = 100;
btn1.FontSize = 12;
btn1.HorizontalAlignment = System.Windows.HorizontalAlignment.Right;
btn1.SetValue(DockPanel.DockProperty, Dock.Right);
btn1.Click += new RoutedEventHandler(btn1_Click);
btn2.Content = "Butten 2";
btn2.Height = 32;
btn2.Width = 100;
btn2.FontSize = 12;
btn2.HorizontalAlignment = System.Windows.HorizontalAlignment.Right;
btn2.SetValue(DockPanel.DockProperty, Dock.Right);
btn2.Click += new RoutedEventHandler(btn2_Click);
#endregion
#region TextBox Properties
txt1.Text = "Text " + (num + 1).ToString();
txt1.Height = 32;
txt1.Width = double.NaN;
txt1.FontSize = 12;
txt1.Padding = new Thickness(0, 7, 0, 7);
#endregion
Grid_Grid.RowDefinitions.Add(new RowDefinition());
Grid_Grid.RowDefinitions[num].Height = new GridLength(66, GridUnitType.Pixel);
Grid_Grid.Children.Add(stack);
stack.SetValue(Grid.RowProperty, num);
num++;
}
}