| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
DevExpress Mobile UI allows you to use a .NET cross-platform UI toolkit and C# to build native apps for iOS and Android.
You need a DevExpress .NET MAUI Subscription to run the example. To evaluate our controls, you can start a free 30-day trial through the DevExpress NuGet feed.
This repository contains a sample application designed to display historical stock information (past three months) for companies listed on NASDAQ. The app includes two screens. The main app screen uses the DevExpress Collection View for .NET MAUI to display a list of stock symbols.
The second screen uses the DevExpress Chart View for .NET MAUI to display historical data (daily open-close-high-low stock prices and transaction volume).
The following step-by-step tutorial details how to reproduce this application.
You can also call the following command in a CLI to create a new .NET MAUI project:
dotnet new maui -n Stocks
Collection View and Charts for .NET MAUI support both iOS and Android. Your project cannot target MacCatalyst and/or Windows. To remove them, right-click the project and click Edit Project File. Remove any references to Windows and MacCatalyst. Use the project file in this repository as an example. In addition, please remove MacCatalyst and Windows folders from the Platforms folder in Solution Explorer.
Our main page displays a list of companies. In the MainPage.xaml file, you must:
<ContentPage
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://schemas.microsoft.com/dotnet/2021/maui/design"
xmlns:dxcv="clr-namespace:DevExpress.Maui.CollectionView;assembly=DevExpress.Maui.CollectionView"
xmlns:local="clr-namespace:Stocks"
Title="Market"
BackgroundColor="{DynamicResource BackgroundColor}"
x:Class="Stocks.MainPage">
<dxcv:DXCollectionView/>
</ContentPage>The .NET MAUI Framework requires a registered handler for all third-party controls used in an application. Review the following Microsoft help topic for more information: Register handlers.
In the MauiProgram.cs file, call the UseDevExpress method to register handlers for the DXCollectionView, ChartView, and other DevExpress controls.
using DevExpress.Maui.CollectionView;
using DevExpress.Maui.Charts;
using Microsoft.Maui;
using Microsoft.Maui.Hosting;
using Microsoft.Maui.Controls.Hosting;
using Microsoft.Maui.Controls.Xaml;
[assembly: XamlCompilationAttribute(XamlCompilationOptions.Compile)]
namespace Stocks {
public static class MauiProgram {
public static MauiApp CreateMauiApp() {
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.UseDevExpress()
.ConfigureFonts(fonts => {
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
});
return builder.Build();
}
}
}If we run the application as it stands, it will display an empty collection view on the main page. The next step is to populate the app with data. This sample application follows the MVVM pattern. To store data, the application uses the following classes:
Create a new class in the project and paste the following code:
using System;
using System.Collections.Generic;
namespace Stocks {
public class Symbol {
public string Ticker { get; set; }
public string Name { get; set; }
public IList<StockPrice> Prices { get; set; }
}
public class StockPrice {
public DateTime Date { get; set; }
public double Open { get; set; }
public double Close { get; set; }
public double High { get; set; }
public double Low { get; set; }
public double Volume { get; set; }
}
}Most mobile applications use a REST API to obtain data from a web service. The response can be formatted in HTML, XML, JSON, or any other format. This sample application uses static data formatted in JSON and stored in a file. The JSON file contains an array of companies, with an array of daily historical stock prices for each.
Download the symbols.json file, and add this file to the solution. Right-click the project, click Add > Existing Item. Once added, right-click the file, go to properties, and set Build Action to Embedded resource.
Specify the logical name in the project file as shown below.
<EmbeddedResource Include="symbols.json">
<LogicalName>symbols</LogicalName>
</EmbeddedResource>Use the code below to read data from the file and populate the data source. Create a new class in the project as follows:
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
namespace Stocks {
public static class Data {
static readonly string logicalName = "symbols";
static IList<Symbol> symbols;
public static IList<Symbol> Symbols {
get {
if (symbols == null) {
symbols = GetSymbols();
}
return symbols;
}
}
static IList<Symbol> GetSymbols() {
List<Symbol> symbols = null;
using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(logicalName))
using (StreamReader reader = new(stream, Encoding.UTF8)) {
var json = reader.ReadToEnd();
symbols = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Symbol>>(json);
}
return symbols;
}
}
}You should install the Newtonsoft.Json package to deserialize data. Review the following Microsoft help topic for more information: Install and use a package in Visual Studio.
To proceed, we will need to create a view model for the main page. As the data source contains an array of daily stock prices, we can display the price change next to each company in the list. The code below defines the following view models:
using System;
using System.Collections.Generic;
namespace Stocks {
public class MainViewModel {
public IList<ItemViewModel> Items { get; set; }
public MainViewModel() {
Items = new List<ItemViewModel>();
foreach (Symbol symbol in Data.Symbols) {
var symbolViewModel = new ItemViewModel();
symbolViewModel.Ticker = symbol.Ticker;
symbolViewModel.CompanyName = symbol.Name;
symbolViewModel.Change = symbol.Prices[0].Close - symbol.Prices[1].Close;
symbolViewModel.ChangePercent = symbol.Prices[0].Close / symbol.Prices[1].Close - 1;
symbolViewModel.Date = symbol.Prices[0].Date;
symbolViewModel.ClosePrice = symbol.Prices[0].Close;
Items.Add(symbolViewModel);
}
}
}
public class ItemViewModel {
public string Ticker { get; set; }
public string CompanyName { get; set; }
public double ClosePrice { get; set; }
public double Change { get; set; }
public double ChangePercent { get; set; }
public DateTime Date { get; set; }
}
}We can now update main page markup so it displays data from the view model. We set the ContentPage.BindingContext property to a view model object and bind the DXCollectionView.ItemsSource property to a collection in this view model.
<ContentPage.BindingContext>
<local:MainViewModel/>
</ContentPage.BindingContext>
<ContentPage.Content>
<dxcv:DXCollectionView
ItemsSource="{Binding Items}">
</dxcv:DXCollectionView>
</ContentPage.Content>The DXCollectionView.ItemTemplate property allows you to specify a data template used to render items in the view. In this example, the template contains multiple labels and an image. Review the following Microsoft help topic for more information on data templates: Creating a Data Template.
Let's create a grid layout in the item template.
<dxcv:DXCollectionView.ItemTemplate>
<DataTemplate>
<Grid
Margin="0"
BackgroundColor="Transparent"
RowSpacing="0"
ColumnSpacing="0"
Padding="8,0"
ColumnDefinitions="*, *"
RowDefinitions="Auto, Auto, 1">
</Grid>
</DataTemplate>
</dxcv:DXCollectionView.ItemTemplate>We also need to populate the grid layout with labels and images.
<Label
Text="{Binding Ticker}"
Margin="12,12,0,0"
VerticalOptions="End"
TextColor="{DynamicResource PrimaryTextColor}"
FontSize="Medium"/>
<Label
Text="{Binding CompanyName}"
Grid.Row="1"
Margin="12,4,0,12"
VerticalOptions="Start"
FontSize="Caption"
TextColor="{DynamicResource SecondaryTextColor}"/>
<Label
Text="{Binding ClosePrice}"
Grid.Column="1"
VerticalOptions="End"
Margin="0,12,12,0"
HorizontalOptions="End"
FontSize="Medium"
TextColor="{DynamicResource PrimaryTextColor}"/>
<dx:DXStackLayout
Orientation="Horizontal"
Grid.Column="1"
Grid.Row="1"
VerticalOptions="Start"
HorizontalOptions="EndAndExpand"
Margin="0,4,12,12">
<dx:DXImage
Source="{Binding Change, Converter={local:DoubleToImageSourceConverter
PositiveValue='quote_arrow_up',
NegativeValue='quote_arrow_down',
ZeroValue='not_changed'}}"
WidthRequest="18"
HeightRequest="18"
VerticalOptions="Start"
Margin="0,0,3,0"/>
<Label
Text="{Binding Change, StringFormat='{0:+0.00;-0.00;0.00}'}"
TextColor="{Binding Change, Converter={local:DoubleToColorConverter
PositiveValue='RisingValueColor',
NegativeValue='FallingValueColor',
ZeroValue='TextColor'}}"
VerticalOptions="Start"
FontSize="Caption"
Margin="3,0"/>
<Label
Text="{Binding ChangePercent, StringFormat='{0:(+0.00%);(-0.00%);(0.00%)}'}"
TextColor="{Binding Change, Converter={local:DoubleToColorConverter
PositiveValue='RisingValueColor',
NegativeValue='FallingValueColor',
ZeroValue='TextColor'}}"
VerticalOptions="Start"
Margin="3,0,0,0"
FontSize="Caption"/>
</dx:DXStackLayout>
<dx:DXSeparator
Grid.Row="2"
Grid.ColumnSpan="2"
Color="{DynamicResource SeparatorColor}"
Margin="12,0"/>As you may notice, the markup uses a converter to display an up or down arrow (depending on price increase/decrease). You can find the appropriate image files in the Images folder. Copy these files to the Resources folder in your project. The text color also depends on price change. To learn more about converters, review the following Microsoft help topic: Binding Value Converters.
using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls.Xaml;
using Microsoft.Maui.Graphics;
using System;
using System.Globalization;
namespace Stocks {
public class DoubleToImageSourceConverter : IValueConverter, IMarkupExtension<DoubleToImageSourceConverter> {
public ImageSource ZeroValue { get; set; } = string.Empty;
public ImageSource PositiveValue { get; set; } = string.Empty;
public ImageSource NegativeValue { get; set; } = string.Empty;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
if (!(value is double doubleValue) || targetType != typeof(ImageSource)) return null;
switch (doubleValue) {
case > 0: return PositiveValue;
case < 0: return NegativeValue;
default: return ZeroValue;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotSupportedException();
}
public object ProvideValue(IServiceProvider serviceProvider) {
return this;
}
DoubleToImageSourceConverter IMarkupExtension<DoubleToImageSourceConverter>.ProvideValue(IServiceProvider serviceProvider) {
return this;
}
}
public class DoubleToColorConverter : IValueConverter, IMarkupExtension<DoubleToColorConverter>{
public string ZeroValue { get; set; } = string.Empty;
public string PositiveValue { get; set; } = string.Empty;
public string NegativeValue { get; set; } = string.Empty;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
if (!(value is double doubleValue) || targetType != typeof(Color)) return null;
switch (doubleValue) {
case > 0: return (Color)Application.Current.Resources[PositiveValue];
case < 0: return (Color)Application.Current.Resources[NegativeValue];
default: return (Color)Application.Current.Resources[ZeroValue];
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotSupportedException();
}
public DoubleToColorConverter ProvideValue(IServiceProvider serviceProvider) {
return this;
}
object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider) {
return this;
}
}
}The application applies a dark theme to controls. You can find colors and styles in the DarkTheme.xaml and SharedStyles.xaml files within this repository. Create similar styles and colors in your project and add them to the resource dictionary in the App.xaml file.
You can now execute the application. Your main page should now display a list of companies.
The second page displays historical data (using our .NET MAUI Chart component). Create a new Content Page in Visual Studio and name it HistoricalDataPage. Do the following in the markup:
<ContentPage
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:dxc="clr-namespace:DevExpress.Maui.Charts;assembly=DevExpress.Maui.Charts"
xmlns:local="clr-namespace:Stocks"
x:Class="Stocks.HistoricalDataPage">
<ContentPage.Content>
<dxc:ChartView/>
</ContentPage.Content>
</ContentPage>Ensure the page's Build Action is set to MauiXaml.
We’ll need to create a view model for this page and populate it with data. The code below defines the HistoricalDataViewModel class and exposes the following properties:
using System;
using System.Collections.Generic;
using System.Linq;
namespace Stocks {
public class HistoricalDataViewModel {
public ItemViewModel Item { get; set; }
public IList<StockPrice> StockPrices { get; set; }
public DateTime RangeStart { get; set; }
public DateTime RangeEnd { get; set; }
public HistoricalDataViewModel(ItemViewModel item) {
Item = item;
Symbol symbol = Data.Symbols.Where(s => s.Ticker == this.Item.Ticker).First();
RangeStart = symbol.Prices.First().Date;
RangeEnd = RangeStart.AddDays(-60);
StockPrices = new List<StockPrice>();
foreach(StockPrice price in symbol.Prices) {
StockPrices.Add(price);
}
}
}
}We can now update the historical data page so it displays data from the view model. We set the ContentPage.BindingContext property to a view model object in the page constructor.
using Microsoft.Maui.Controls;
namespace Stocks {
public partial class HistoricalDataPage : ContentPage {
public HistoricalDataPage(HistoricalDataViewModel viewModel) {
InitializeComponent();
BindingContext = viewModel;
Title = viewModel.Item.Ticker;
}
}
}At the top of the page, the app displays company name and the last price. Below company name, the app displays a chart. We place these elements within a grid layout.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="115"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
</Grid>The first grid row contains company name and the last price. We use labels and images to display this information.
<dx:DXStackLayout
Grid.Row="0" Grid.Column="0"
BackgroundColor="Transparent"
Orientation="Vertical"
HorizontalOptions="StartAndExpand"
VerticalOptions="CenterAndExpand"
ItemSpacing="0"
Margin="12">
<Label
Text="{Binding Item.CompanyName}"
TextColor="{DynamicResource TextColor}"
FontSize="Subtitle"
Margin="0,0,12,0"/>
<dx:DXStackLayout
Orientation="Horizontal"
ItemSpacing="0"
HorizontalOptions="StartAndExpand">
<Label
Text="{Binding Item.ClosePrice, StringFormat='{0:0.00}'}"
TextColor="{DynamicResource TextColor}"
FontSize="Title"
Margin="0,0,12,0"
VerticalOptions="End"
VerticalTextAlignment="End"
LineBreakMode="TailTruncation"/>
<dx:DXImage
WidthRequest="18"
HeightRequest="18"
HorizontalOptions="End"
Margin="0,0,3,0"
Source="{Binding Item.Change,
Converter={local:DoubleToImageSourceConverter
PositiveValue='quote_arrow_up',
NegativeValue='quote_arrow_down',
ZeroValue='not_changed'}}"
VerticalOptions="End">
<dx:DXImage.WidthRequest>
<OnPlatform x:TypeArguments="x:Double">
<On Platform="Android" Value="20"/>
<On Platform="iOS" Value="24"/>
</OnPlatform>
</dx:DXImage.WidthRequest>
<dx:DXImage.HeightRequest>
<OnPlatform x:TypeArguments="x:Double">
<On Platform="Android" Value="20"/>
<On Platform="iOS" Value="24"/>
</OnPlatform>
</dx:DXImage.HeightRequest>
</dx:DXImage>
<Label
Text="{Binding Item.Change, StringFormat='{0:+0.00;-0.00;0.00}'}"
TextColor="{Binding Item.Change,
Converter={local:DoubleToColorConverter
PositiveValue='RisingValueColor',
NegativeValue='FallingValueColor',
ZeroValue='TextColor'}}"
HorizontalOptions="End"
VerticalOptions="End"
FontSize="Caption"
Margin="3,0"/>
<Label
Text="{Binding Item.ChangePercent, StringFormat='{0:(+0.00%);(-0.00%);(0.00%)}'}"
TextColor="{Binding Item.Change,
Converter={local:DoubleToColorConverter
PositiveValue='RisingValueColor',
NegativeValue='FallingValueColor',
ZeroValue='TextColor'}}"
HorizontalOptions="End"
VerticalOptions="End"
Margin="3,0,0,0"
FontSize="Caption"/>
</dx:DXStackLayout>
<Label
Text="{Binding Item.Date, StringFormat='Date: {0:d}'}"
TextColor="{DynamicResource SecondaryTextColor}"
FontSize="Caption"/>
</dx:DXStackLayout>We place the chart in the second row.
<dxc:ChartView
Theme="Dark"
Grid.Row="1"
AxisXNavigationMode="ScrollingAndZooming"
AxisMaxZoomPercent="100000">
<dxc:ChartView.ChartStyle>
<dxc:ChartStyle
BackgroundColor="{StaticResource BackgroundColor}">
<dxc:ChartStyle.Padding>
<dxc:Padding Left="8" Right="8"/>
</dxc:ChartStyle.Padding>
</dxc:ChartStyle>
</dxc:ChartView.ChartStyle>
</dxc:ChartView>The ChartView.AxisX and ChartView.AxisY properties allow you to configure chart axes:
<dxc:ChartView.AxisX>
<dxc:DateTimeAxisX
x:Name="axisX"
EmptyRangesVisible="False"
MeasureUnit="Day">
<dxc:DateTimeAxisX.Range>
<dxc:DateTimeRange
SideMargin="3"
VisualMin="{Binding RangeStart}"
VisualMax="{Binding RangeEnd}"/>
</dxc:DateTimeAxisX.Range>
</dxc:DateTimeAxisX>
</dxc:ChartView.AxisX>
<dxc:ChartView.AxisY>
<dxc:NumericAxisY
AlwaysShowZeroLevel="False"
AutoRangeMode="VisibleValues">
<dxc:NumericAxisY.DisplayPosition>
<dxc:AxisDisplayPositionFar/>
</dxc:NumericAxisY.DisplayPosition>
<dxc:NumericAxisY.Layout>
<dxc:AxisLayout Anchor1="0.333" Anchor2="1.0" />
</dxc:NumericAxisY.Layout>
<dxc:NumericAxisY.Label>
<dxc:AxisLabel Position="Inside" TextFormat="$#.#"/>
</dxc:NumericAxisY.Label>
<dxc:NumericAxisY.Style>
<dxc:AxisStyle
LineVisible="False"
MajorGridlinesVisible="True"
MajorGridlinesColor="{StaticResource SeparatorColor}"/>
</dxc:NumericAxisY.Style>
</dxc:NumericAxisY>
</dxc:ChartView.AxisY>The CandleStickSeries contains open-close-high-low stock prices. The CandleStickSeries.Data property is set to a SeriesDataAdapter object. This object interprets bound data source fields. To specify data source fields with data, we use ValueDataMember objects. Review the following topic for more information: Data Adapters.
<dxc:ChartView.Series>
<dxc:CandleStickSeries>
<dxc:CandleStickSeries.Data>
<dxc:SeriesDataAdapter
DataSource="{Binding StockPrices}"
ArgumentDataMember="Date">
<dxc:ValueDataMember Type="Open" Member="Open"/>
<dxc:ValueDataMember Type="High" Member="High"/>
<dxc:ValueDataMember Type="Low" Member="Low"/>
<dxc:ValueDataMember Type="Close" Member="Close"/>
</dxc:SeriesDataAdapter>
</dxc:CandleStickSeries.Data>
</dxc:CandleStickSeries>
</dxc:ChartView.Series>We assign a CandleStickSeriesStyle object to the CandleStickSeries.Style property to specify candlestick-related appearance settings.
<dxc:CandleStickSeries.Style>
<dxc:CandleStickSeriesStyle
RisingFill="{StaticResource RisingValueColor}"
RisingStroke="{StaticResource RisingValueColor}"
FallingFill="{StaticResource FallingValueColor}"
FallingStroke="{StaticResource FallingValueColor}"/>
</dxc:CandleStickSeries.Style>The BarSeries display data as bars. We use Bar charts to display daily stock volumes.
<dxc:BarSeries>
<dxc:BarSeries.Data>
<dxc:SeriesDataAdapter DataSource="{Binding StockPrices}"
ArgumentDataMember="Date">
<dxc:ValueDataMember Type="Value"
Member="Volume" />
</dxc:SeriesDataAdapter>
</dxc:BarSeries.Data>
<dxc:BarSeries.Style>
<dxc:BarSeriesStyle
Fill="{StaticResource SymbolDetailPage_VolumeChartColor}"
Stroke="{StaticResource SymbolDetailPage_VolumeChartColor}"/>
</dxc:BarSeries.Style>
</dxc:BarSeries>We can use the BarSeries.AxisY property to specify the Y-axis. A NumericAxisY object allows you to specify the following settings:
<dxc:BarSeries.AxisY>
<dxc:NumericAxisY
AutoRangeMode="VisibleValues">
<dxc:NumericAxisY.LabelValueNotation>
<dxc:AxisLabelEngineeringNotation/>
</dxc:NumericAxisY.LabelValueNotation>
<dxc:NumericAxisY.Layout>
<dxc:AxisLayout Anchor1="0" Anchor2="0.333" />
</dxc:NumericAxisY.Layout>
<dxc:NumericAxisY.DisplayPosition>
<dxc:AxisDisplayPositionFar/>
</dxc:NumericAxisY.DisplayPosition>
<dxc:NumericAxisY.Label>
<dxc:AxisLabel Position="Inside" TextFormat="$#">
<dxc:AxisLabel.Style>
<dxc:AxisLabelStyle>
<dxc:AxisLabelStyle.TextStyle>
<dxc:TextStyle Color="{StaticResource TextColor}"/>
</dxc:AxisLabelStyle.TextStyle>
</dxc:AxisLabelStyle>
</dxc:AxisLabel.Style>
</dxc:AxisLabel>
</dxc:NumericAxisY.Label>
<dxc:NumericAxisY.Style>
<dxc:AxisStyle
LineVisible="False"
MajorGridlinesVisible="True"
MajorGridlinesColor="{StaticResource SeparatorColor}"/>
</dxc:NumericAxisY.Style>
</dxc:NumericAxisY>
</dxc:BarSeries.AxisY>When a user taps a company in the list on the main page, the application displays historical data for that company on the second page. Let's wrap the main page in a NavigationPage to support navigation from the main page to the second page (and back). Update the App.xaml.cs file as follows:
using Microsoft.Maui.Controls;
using Application = Microsoft.Maui.Controls.Application;
namespace Stocks {
public partial class App : Application {
public App() {
InitializeComponent();
MainPage = new NavigationPage(new MainPage());
}
}
}In the MainPage.xaml file and the code-behind, handle the DXCollectionView.Tap event as follows:
using DevExpress.Maui.CollectionView;
private async void DXCollectionView_Tap(object sender, CollectionViewGestureEventArgs e) {
var symbolViewModel = (ItemViewModel)e.Item;
var historicalDataViewModel = new HistoricalDataViewModel(symbolViewModel);
Navigation.PushAsync(new HistoricalDataPage(historicalDataViewModel));
}Let’s execute the application once more. Users can now tap a company name on the main page and analyze the company's historical data on the second page.
(you will be redirected to DevExpress.com to submit your response)
| Back | FazBrowse Home | New Git URL |