ASP.NET中实时图表的实现,第1张

 在对大批量的数据进行分析比较时 最常用也是最直观明了的表现方法莫过于绘制趋势图表 一般情况下 我们利用EXCEL制作各种类型的趋势图表 但它们都是基于静态数据的 即数据是事先整理好的而不 是动态生成的 如果在网上发布 只能将绘制好的图表以静态GIF图像发布 这无法从根本上满足不同用户对不同数据的需求

 ASP擅长服务器端的Web编程 操作后台数据库更是它的强项 但是用ASP制作实时数据库图表有点困难 因为ASP本身并不支持图表功能 只能借助第三方控件进行开发 如VB的MSChart控件 微软推出的 NET Framework较好地解决了这个问题 微软在 NET平台上集成了实时数据库图表制作组件—OWC(Microsoft Office Web Components) 通过在ASP NET页面中调用OWC 我们可以轻松地绘制出各种类型的实时图表 OWC支持近 种图表类型 包括曲线图 折线图 柱状图 面积图 K线图等 与MSChart相比 OWC功能强大 操作简单 此外 由于OWC是基于服务端的 而MSChart只能应用在客户端 因此在服务器端的Web开发中 MSChart要比OWC逊色不少

 下面笔者将结合实例来具体阐述OWC在ASP NET页面中的应用 这个实例是笔者开发的项目《化纤产品及其原料市场分析系统》中的一个子系统 笔者在该项目中用到OWC 充分享受到了OWC的强大功能给开发工作带来的方便

 三层结构

 系统整体架构采用了B/S三层结构模式 将系统分为用户界面层(也称为表现层) 业务逻辑层(也称为功能层)和数据库服务层(也称为数据层) 开发平台则采用了 NET Framework 有效地降低了系统对客户机的要求 避免了在客户机上分发应用程序与版本控制的困难

 ● 用户界面层 用户界面采用的是ASP NET技术 ASP NET技术的应用增强了系统的通用性 客户端只需安装IE或Netscape等任一款浏览器 无需加载任何组件

 ● 业务逻辑层 采用了 NET Framework调用OWC的技术 能够根据用户的要求快速取得数据库中的数据动态生成图表 系统能够支持复杂的检索条件 检索速度快 响应时间短

 ● 数据库服务层 数据库服务层可采用任何一款关系型数据库 在本项目中 笔者使用的是SQL Server 它能与 NET Framework无缝集成 数据库存取技术则采用了ADO NET

 下文我们将着重介绍业务逻辑层的实现方法

 图表元素简介

 一张完整的图表由若干个元素组成 我们必须对它们有所了解 才能随心所欲 充分自如地对图表进行全方位的控制 也才能更好地理解本程序 笔者制作了一张简易的图表 在图中标注了程序涉及到的主要部位和元素的名称 借此帮助读者掌握OWC以及理解本文所引用的代码

 

 使用OWC组件

 在这一节里所涉及的源代码摘自于《化纤产品及其原料市场分析系统》 该系统在Window /XP简体中文专业版 NET Framewrok 环境下通过 使用OWC组件的步骤如下

  在当前目录中新建一个存放图表文件的子目录chart 同时把对该目录的 修改 权限赋予ASP NET账户 具体步骤如下 用鼠标右键单击chart目录名 选择 属性 菜单项 在弹出的 Chart 属性对话框中单击 安全 选项卡 再单击 添加 按钮 找到ASP NET账户 赋予 修改 权限 单击 确定 按钮结束 这样 ASP NET就可以在chart目录中写入图表文件了

  定义一个服务器端的Image图像控件 该图像的属性imageURL将在程序末尾被指向动态生成的图表文件 因此在这里无需为它赋值

 < asp image id= imgChart Width= Height= Visible= False Runat= server >< /asp image>

  添加OWC引用

 在使用OWC之前 首先必须将OWC的引用加入到 解决方案资源管理器 中 具体步骤如下 打开 解决方案资源管理器 面板 鼠标右键单击 引用 选择 添加引用 菜单 在弹出的 添加引用 对话框中单击 卡片 找到 Microsoft Office Web Components 单击 选择 和 确定 按钮 OWC就被添加到了引用中

  定义OWC空间 并在该空间中加入一个OWC图表owcChart

 Dim owcChartSpace As OWC ChartSpace = New OWC ChartSpace()

 Dim owcChart As OWC WCChart = owcChartSpace Charts Add

  用SQL检索条件进行数据库检索 并将检索结果以RecordSet数据集的方式赋给owcChart

 OWC只支持RecordSet数据集 不支持DataSet数据集 因此在检索时不能使用sqlCommand sqlDataAdapter等对象 只能使用RecordSet对象进行检索

  打开connection连接

 ConnADO Open(connectionString)

 RecordsetADO ActiveConnection = ConnADO

  设置游标为静态游标

 RecordsetADO CursorType = ADODB CursorTypeEnum adOpenStatic

 RecordsetADO CursorLocation = ADODB CursorLocationEnum adUseClient

  变量strSQL中存放了标准SQL检索条件

 RecordsetADO Open(strSQL)

 然后将RecordSet数据集赋给OWC对象

 owcChartSpace DataSource = RecordsetADO

 在本例中 我们假定用SQL语句检索出的数据共有三个字段 产品 日期和价格 这三个字段的值分别与图表中的曲线 分类(X)轴和数值(Y)轴的数据一一对应

  确定曲线类型 并确定区别不同曲线的字段名

 首先确定曲线类型为平滑曲线

 owcChart Type = OWC ChartChartTypeEnum chChartTypeSmoothLine

 OWC支持在同一张图表中显示两条以上的曲线 因此我们必须给出区别不同曲线的依据 这个依据就是 产品 字段的取值 具体地说 产品 字段中有几个不同的取值 就会生成几条不同的曲线

 owcChart SetData(OWC ChartDimensionsEnum chDimSeriesNames 产品 )

  确定分类(X)轴标签与数值(Y)轴标签所对应的字段

 首先需要定义owcSeries为OWC的曲线集合 然后遍历图表中的每一条曲线 将 日期 字段的值赋给分类(X)轴作为X轴刻度标签 将 价格 字段的值赋给数值(Y)轴作为Y轴刻度标签 如果我们能够确定图表中只有一条曲线 也可以省略遍历的过程 但这样无疑会降低程序的通用性

 Dim owcSeries As OWC WCSeries

 For Each owcSeries In owcChart SeriesCollection

 owcSeries SetData(OWC ChartDimensionsEnum chDimCategories 日期 )

 owcSeries SetData(OWC ChartDimensionsEnum chDimValues 价格 )

 Next

  对坐标轴的属性进行设置

 这部分代码通过对坐标轴标题的文字内容 颜色 大小 主要和次要刻度线及其标签 主要和次要网络线等方面的设置美化图表 读者如果对本段代码中的概念有些模糊 可以参考前一部分提供的那张图表 具体设置方法请参见以下代码

  先定义axis为坐标轴集合

 Dim axis As OWC WCAxis

  遍历坐标轴集合

 For Each axis In owcChart Axes

  显示轴标题

 axis HasTitle = True

  先对分类(X)轴进行设置

 If axis Type=OWC ChartAxisTypeEnum

 chCategoryAxis Then

 axis HasTickLabels = True

  显示X轴刻度标签

 axis Position = OWC ChartAxisPositionEnum chAxisPositionBottom

  标签的显示位置

 axis Title Font Color = blue

  X轴的标题文字颜色

 axis Title Font Size =

  X轴的标题文字大小

 axis Title Caption = 日期范围

  X轴的标题文字内容

 Else

  对数值(Y)轴进行设置

 axis MajorGridlines Line Color = silver

  Y轴主要网络线的颜色

 axis MajorTickMarks = OWC ChartTickMarkEnum chTickMarkNone

  不显示Y轴主要刻度标记

 axis HasTickLabels = True

  显示Y轴刻度标签

 axis Title Font Color = blue

  Y轴的标题文字颜色

 axis Title Font Size =

  Y轴的标题文字大小

 axis Title Caption= 价格(千元/吨)

  Y轴的标题文字内容

 End If

 Next

  以GIF图像格式输出图表 并将图像文件名赋给Image控件

  用随机数来生成随机文件名

 Randomize()

 Dim nFileNameSuffix As Integer

 Dim sFileNameSuffix As String

 nFileNameSuffix = Rnd()

 sFileNameSuffix = System Convert ToString(nFileNameSuffix)

  以GIF格式输出图表 大小为 图表的文件名为 polyesterprice_随机数 gif 存放在chart子目录中

 owcChartSpace ExportPicture(MapPath( chart/PolyesterPrice_ ) + sFileNameSuffix + gif gif )

  将Image控件的URL指向该图表文件

 imgChart ImageUrl= chart/PolyesterPrice_ + sFileNameSuffix + gif

 通过以上九个步骤 我们就完成了一个实时数据库图表的生成与显示 在此需要指出的是 以上的九个步骤只是生成一张图表必不可少的基本过程 通过设置OWC的其他属性可以更好 更精确地控制图表的生成与显示方式 如图例 线条的粗细与颜色 坐标轴刻度线及标签的显示频度 网络线等 这部分笔者不再介绍 请参见本文第四部分的源代码

 本文代码生成的图表效果请见下图

 

 优化

 上文中所有实时生成的图表文件都存放在chart文件夹中 由于采用了随机文件名的方式 因此这些文件不会互相覆盖 但是如此日积月累 越来越多的文件不仅占用了硬盘空 间 也妨碍了管理 降低了性能 我们能不能在程序中自动删除以前的图表文件呢?答案是肯定的 我们只要在代码文件的Page_Load()函数中放置如下一段代码 程序运行的时候 就会自动删除当日以前的文件 这样 chart文件夹中存放的就总是当日生成的图表文件 从而有效地避免了文件垃圾

  先取得chart文件夹中的文件列表

 Dim fileEntries() As String = System IO Directory GetFiles(MapPath( chart ))

 Dim sFile As String

  遍历文件列表

 For Each sFile In fileEntries

  将文件的生成日期与系统日期相比 如果是当日以前生成的文件 删除它

 If DateTime Compare(System IO File GetCreationTime(sFile) AddDays( ) DateTime Now) < Then

 System IO File Delete(sFile)

 End If

 Next

lishixinzhi/Article/program/net/201311/11850

1·绑定数据源来进行连接

2用代码连接

先到数据库建立一个数据库和相应的表

连接数据库的代码:

Dim str As String = "Data Source=服务器名;Initial Catalog=数据库名;Persist Security Info=True;User ID=;Password="

dim conn As SqlClientSqlConnection

try

conn = New SqlClientSqlConnection

connConnectionString = str

connOpen()

Return True

Catch ex As Exception

MsgBox(exToString)

Return False

End Try

登录代码:Dim str As String = "Data Source=服务器名;Initial Catalog=数据库名;Persist Security Info=True;User ID=;Password="

dim conn As SqlClientSqlConnection

conn = New SqlClientSqlConnection

connConnectionString = str

connOpen()

sqlstr = "Select From Amd Where AmdName='" & TextBox1Text & "' And AmdPwd = '" & TextBox2Text & "'"

Dim sqlcmd As SqlClientSqlCommand = New SqlClientSqlCommand(sqlstr, conn)

Dim dr As SqlClientSqlDataReader

dr = sqlcmdExecuteReader

If drRead = True Then '判断一条记录为真

kfShow() '显示下个窗体

MeHide() ’隐藏当前窗体

Else

MessageBoxShow("输入信息有误!", "提示")

TextBox1Text = ""

TextBox2Text = ""

End If

 Swing 工具包提供各种用于创建用户界面的工具和几乎令人眼花缭乱的选项 这些选项用于在程序生存期间修改界面 小心地使用这些功能可以导致界面能够适应用户的需要并简化交互过程 粗心地使用同样的功能可以导致非常混乱或彻底不可用的程序 本文介绍动态 UI 的技术和体系 并提供有关构建有效的界面的帮助 您将修改随 Sun JDK 一起提供的基于 SwingSet 示例应用程序的源代码;此应用程序的 UI 使用许多动态的特性并且可以作为理解它们的极好的起点

  禁用小部件

 动态 UI 的最简单形式是使不可用的菜单项或按钮变灰的 UI 禁用 UI 小部件与禁用所有小部件的方法都是相同的 setEnabled() 函数是 Component 类的一个功能 清单 显示了禁用按钮的代码

  清单 禁用按钮

  button setEnabled(false);

 正如您看到的 十分简单 关键问题是何时应该 启用或禁用一个按钮 通常的设计决策是当按钮不可用时禁用它 例如 当一个文件从上一次保存以来还没有被修改时 很多程序禁用 Save 按钮(以及任何相应的菜单项)

 关于禁用按钮的重要警告是要记住在适当的时候重新启用它们 例如 如果在单击按钮和按钮的动作完成之间有一个确认步骤 即使确认失败也应该重新启用按钮

 调整范围

 有时 应用程序需要动态地调整数值小部件的范围 例如 Spinner 或者 Slider 这可能比它看起来要复杂许多 特别是 Slider 有二级功能 —— 刻度 刻度间隔和标签 —— 这些可能需要随着范围的调整而加以调整以避免灾难发生

 SwingSet 示例没有进行任何一项调整 所以您需要通过把 ChangeListener 连接到一个可以修改其他滑块的滑块来修改它 输入新的 SliderChangeListener 类 如清单 所示

  清单 更改滑块的范围

  class SliderChangeListener implements ChangeListener {        JSlider h;        SliderChangeListener(JSlider h) {               this h = h;        }        public void stateChanged(ChangeEvent e) {            JSlider js = (JSlider) e getSource();            int i = js getValue();            h setMaximum(i);            h repaint();        } }

 当创建第三个水平滑块时(最初示例中的滑块在每个单位处带有标记 在 和 等处带有标签) 另外还创建了一个新的 SliderChangeListener 它把滑块作为构造器参数传递 当创建第三个垂直的滑块(范围从 到 )时 新的 SliderChangeListener 作为变更监听器添加到它 这大致按预期的那样工作 调整垂直滑块改变了水平滑块的范围

 不幸的是 刻度和标签根本不能很好地工作 当范围变得不是太大时 每五个刻度处的标签能正确地工作 但是刻度 处的额外标签很快成为一个可用性问题 如图 所示

  图 一起运行的标签

 

  更新刻度和标签

 明显的解决方案是 只要滑块的最大值被更新 就在水平滑块上简单地设置刻度间隔 如清单 所示

  清单 设置刻度间隔

  // DOES NOT WORK int tickMajor tickMinor; tickMajor = (i > ) (i / ) : ; tickMinor = (tickMajor > )   (tickMajor / ) : tickMajor; h setMajorTickSpacing(tickMajor); h setMinorTickSpacing(tickMinor); h repaint();

 目前清单 是正确的 但是它没有引起画在屏幕上的标签的任何变化 必须使用 setLabelTable() 分别设置标签 添加额外一行修复它

  h setLabelTable(h createStandardLabels(tickMajor));

 这仍然出现在刻度 处存在最初设置的标签这样的错误 当然本来的意图是想在滑块的最右端始终有一个标签 可以通过删除旧的标签(在设置新的最大值之前)然后添加一个新的标签达到这一目的 此代码 几乎 可以工作

  清单 替换标签

  public void stateChanged(ChangeEvent e) {        JSlider js = (JSlider) e getSource();        int i = js getValue();        // clear old label for top value        h getLabelTable() remove(h getMaximum());        h setMaximum(i);        int tickMajor tickMinor;        tickMajor = (i > ) (i / ) : ;        tickMinor = (tickMajor > ) (tickMajor / ) : tickMajor;        h setMajorTickSpacing(tickMajor);        h setMinorTickSpacing(tickMinor);        h setLabelTable(h createStandardLabels(tickMajor));        h getLabelTable() put(new Integer(i)        new JLabel(new Integer(i) toString() JLabel CENTER));        h repaint(); }

 如果我已经告诉过您一次 那么我就已经告诉您两次了

 我使用几乎 的意思是 虽然清单 中的代码删除了刻度 处的标签 但是它没有在刻度 i 处添加新标签;相反 只能看到间隔为 tickMajor 的标签 首先此解决方法相当令人讨厌

  清单 强迫显示更新

  h setLabelTable(h getLabelTable());

 这个看起来无意义的操作实际上有重大的作用 每当设置标签表时就生成滑块的标签 没有为了修改对表进行特殊回调 所以添加到表中的新值不必产生效果;很显然 清单 中的空操作具有使 Swing 知道它必须更新显示的副作用 (以免您认为这是我自己发明的 请注意最初的 SwingSet 代码包括这样一个调用 )

 这只留下了一个问题 标签出现在滑块的末端这个非常合理的期望有时使两个标签互相直接相邻 乃至重叠 如图 所示

  图 滑块末端的重叠标签

 

 很多解决此问题的方法都是可行的 编写自己的代码以使用值来填充标签表并停止以前的序列以便序列中的最后标签与滑块的末端有一些隔离 我将把这个作为作业留给您

 在许多情况下 为了启用和禁用菜单项而限制菜单修改是很实际的 此方法容易受到用于禁用项的常规警告的影响 避免由于偶然地禁用重要项而使程序处于不可用状态

 添加或删除菜单项或子菜单也是可能的 修改 JMenuBar 没有这么容易;没有从工具栏上删除和替换单个菜单的接口 如果您想修改工具栏(而不是向最右端添加菜单) 需要制作一个新的工具栏并用它替换旧的工具栏

 修改单个菜单会立即生效;您不必在将菜单连接到工具栏或另一个菜单之前构建一个菜单 当需要修改菜单选项的选择时 最容易的方法是修改选定的菜单 您可能仍然想添加或删除完整的菜单 并且这么做并不是特别难 清单 显示一个将菜单插入到菜单条中给定索引前的方法的简单示例 此示例假定要替换的 JMenuBar 连接到 JFrame 对象 但是任何能使您获得和设置菜单条的东西的工作方式都是一样的

  清单 把一个菜单插入到菜单条中

  public void insertMenu(JFrame frame JMenu menu int index) {        JMenuBar newBar = new JMenuBar();        JMenuBar oldBar = frame getJMenuBar();        MenuElement[] oldMenus = oldBar getSubElements();        int count = oldBar getMenuCount();        int i;        for (i = ; i < count; ++i) {               if (i == index)                      newBar add(menu);               newBar add((JMenu) oldMenus[i]);        }        frame setJMenuBar(newBar); }

 上面的代码我不是开始时就试图编成这样;这是最终的版本 已经很好地修复过了所以它能够运行并反映一些有趣的怪事 初看起来 实现此功能的明显方法似乎应该是使用 getComponentAtIndex() 但是这种方法已经受到了反对 幸运的是 getSubElements() 已经足够好 为 newBar add() 而进行到 JMenu 的强制类型转换可能是安全的 但是我不喜欢这样 getSubElements() 接口不仅对菜单条而且对菜单进行操作 菜单可能具有几种类型的子元素 JMenu 是可以添加到 JMenuBar 的惟一元素 所以必须把元素强制转换为 JMenu 以把它传递到 JMenuBar add() 方法 不幸的是 如果将来的 API 修订版允许将除 JMenu 类型之外的元素添加到 JMenuBar 就不再需要把返回的元素强制转换 JMenu了

 清单 中的代码反映了另外一个相当微妙的界面怪事;菜单数必须提前缓存起来 当把菜单添加到新的菜单条时 它们从旧的菜单条中被删除 虽然看起来相似 但是清单 中的代码不能工作 因为循环提前结束了:

  清单 循环结束太早

  // DOES NOT WORK for (i = ; i < oldBar getMenuCount(); ++i) {        if (i == index)               newBar add(menu);        newBar add((JMenu) oldMenus[i]); }

 清单 中的循环只复制一半数量的菜单 例如 如果开始菜单条上有 个 菜单 它复制前面的两个菜单 复制完第一个菜单以后 i 的值为 并且 getMenuCount() 返回 ;在复制完第二个菜单以后 i 的值为 并且 getMenuCount() 返回 因此循环结束 我没有找到任何介绍通过向菜单条添加菜单从而从另一个菜单条删除菜单这样的特性的文档 因此可能不是有意这样 但是 它很容易达到这个目的

 从菜单条删除菜单稍微容易一些;只是把所有其他的菜单从旧的菜单条复制到新的菜单条 就完成了删除菜单 很容易!

 如果界面使用了很多动态菜单更新 创建一组菜单条并在它们之间切换而不是一直动态地更新它们也许会更好一些 但是 如果如此快地改变菜单 可能会使用户完全发疯

 勘误 在本文的草稿阶段 我忽略了 JMenuBar 类的继承方法的列表 其实 它有 remove 和 add 方法可以用来在指定的索引处进行删除和插入 另外一个教训是 查看继承的方法而不仅仅是特定于类的方法

  调整窗口大小

 所幸的是对于大多数情况 窗口大小调整是自动进行的 但是需要考虑调整大小产生的一些影响 在非常小的窗口中 按钮条 菜单条和类似功能可能变成有问题的 管理程序自身的图形面板需要响应调整大小事件 让 Swing 处理对 UI 元素的包装 但是要密切注视组件的大小;不要获取一次组件的尺寸然后就一直使用这些值

 更微妙的地方是 一些设计决策(例如滑块上刻度的密度)可能被适度地更新以响应窗口大小调整事件 像素宽度的滑块不能具有像 像素宽度的滑块那样多的可读标签 您可能想通过添加全新的有用功能来让 UI 更进一步用在大型显示器上

 但是 在多数情况下 可以忽略窗口大小调整 您不应该做的是不必要地阻止或重写它 布局代码中的窗口一侧的便捷工具不是必需的 最小的窗口大小可能是无可厚非的 但是要让人们能把窗口调整到他们所需要的大小

  一般原则

 Swing 工具包在 UI 设计方面提供了很大的灵活性 如果小心地使用 动态更新界面的选项能够极大地简化该界面;例如 只有应用菜单的选项时 用户才能容易地显示菜单

 不幸的是 一些 API 的特性可能使此方法产生一些离奇的行为 并且副作用和相互影响并不总是像您期望的那样记录下来 如果您有使用动态界面的想法 就要准备在调试上花费一些额外的时间 您可能从 Swing 库的困境中走出来并发现自己需要处理出人意料的行为和/或 bug

 不要让缺乏明显的实现让您气馁 像本文的 JMenuBar 示例所显示的 即使当 API 不支持某个任务时 您也能自己实现它 虽然有一点间接

 尽量不要走极端 当动态 UI 让用户清楚它们的固有限制时 它们才能最好地发挥作用 理想的情况是 用户甚至可能不会注意到界面变化 如果他们能够使用程序的 Object 菜单的惟一时刻是当他们使某个对象被选择时 那么其余的时间他们将不会介意不存在该菜单

 另一方面 如果存在这种可能性 用户不能猜测出一个选项不可用的原因 这时让用户尝试操作并获得包含信息的消息也许会更好 这对于一些操作尤其重要 如果保存选项被禁用 而我想保存数据 那么这不会发生作用 程序可能认为已经保存了数据 但是为什么不让我无论如何都保存它呢如果存在不能保存文件的特殊原因 我可能想知道是什么原因

lishixinzhi/Article/program/Java/hx/201311/26042

  我用VBA在EXCEL2010下做了段程序,可自动生成柏拉图。

  以下是程序源代码

  '数据区域的左上角位置常量

  Const UpperLeftPos = "B4"

  Const UpperPos = "B"

  Const LeftPos = 4

  Sub 绘制标准柏拉图()

  Dim Range1 As Range

  '选择数据区域,Range("B2")这个格子里放的是有效数据的行数,即分类的总个数

  Set Range1 = Range(UpperLeftPos & ":" & Chr(Asc(UpperPos) + 1) & (LeftPos + Range("B2")) & "," & Chr(Asc(UpperPos) + 2) & LeftPos & ":" & Chr(Asc(UpperPos) + 2) & (LeftPos + Range("B2") + 1))

  '插入柱形图

  ActiveSheetShapesAddChartSelect

  ActiveChartChartType = xlColumnClustered

  ActiveChartSetSourceData Source:=Range1

  '把百分比数据系列的图形格式变为“带数据值的折线图”,并画在由次要横坐标和次要纵坐标里

  ActiveChartSeriesCollection(2)Select

  ActiveChartSeriesCollection(2)AxisGroup = 2

  ActiveChartSeriesCollection(2)ChartType = xlLineMarkers

  ActiveChartSetElement (msoElementSecondaryCategoryAxisWithoutLabels)

  ActiveChartSetElement (msoElementSecondaryCategoryAxisShow)

  '把次要横坐标的值显示到刻度上,而不是刻度中间,并隐藏次要横坐标的显示

  ActiveChartAxes(xlCategory, xlSecondary)Select

  SelectionMajorTickMark = xlNone

  SelectionTickLabelPosition = xlNone

  ActiveChartAxes(xlCategory, xlSecondary)AxisBetweenCategories = False

  '调整柱形图的显示,去掉相邻柱子的间距,并给每个柱子加外框

  ActiveChartSeriesCollection(1)Select

  ActiveChartChartGroups(1)GapWidth = 0

  With SelectionFormatLine

  Visible = msoTrue

  ForeColorObjectThemeColor = msoThemeColorText1

  ForeColorTintAndShade = 0

  ForeColorBrightness = 0

  End With

  With SelectionFormatLine

  Visible = msoTrue

  Weight = 1

  End With

  '修改主要纵坐标,满值为累加和

  ActiveChartAxes(xlValue)Select

  ActiveChartAxes(xlValue)CrossesAt = 0

  SelectionMajorTickMark = xlInside

  ActiveChartAxes(xlValue)MinimumScale = 0

  ActiveChartAxes(xlValue)MaximumScale = Range(Chr(Asc(UpperPos) + 3) & (LeftPos + Range("B2") + 1))

  '修改次要纵坐标,满值为100%

  ActiveChartAxes(xlValue, xlSecondary)Select

  ActiveChartAxes(xlValue, xlSecondary)MaximumScale = 1

  ActiveChartAxes(xlValue, xlSecondary)MinimumScale = 0

  SelectionMajorTickMark = xlInside

  ActiveChartAxes(xlValue, xlSecondary)CrossesAt = 1

  SelectionTickLabelsNumberFormat = "0%"

  '让百分比折线上的点显示出具体的数值

  ActiveChartSeriesCollection(2)Select

  ActiveChartSeriesCollection(2)ApplyDataLabels

  ActiveChartSeriesCollection(2)DataLabelsSelect

  SelectionPosition = xlLabelPositionRight

  '隐去横向的主要网格线

  ActiveChartAxes(xlValue)Select

  ActiveChartAxes(xlValue)MajorGridlinesSelect

  SelectionFormatLineVisible = msoFalse

  '为图表添加标题,标题的内容放在Range("B1")格子里

  ActiveChartSetElement (msoElementChartTitleAboveChart)

  ActiveChartChartTitleSelect

  ActiveChartChartTitleText = Range("B1")

  End Sub

创建列数据,填入c列数据,设置c列类型为txt,双击x轴坐标,在scale标签中设置步长为1,在tick label标签中设置type为“text from dataset”,dataset中填入c列地址。见下两图:

Axis对象有个SetScale方法可以设置坐标轴的范围,可以在Surfer自动化帮助中搜索asis object,就可以找到该方法。

以下是方法的部分使用说明:

objectSetScale( Minimum, Maximum, MajorInterval, FirstMajorTick, LastMajorTick, Cross1, Cross2 )

这里的object就是asix,根据你提供的部分代码,可以这样写:

objMapFrameAxes(5)SetScale(2, 5, 1, 2, 5)

1

先将Apple watch的Watch OS系统升级到20版本(iPhone要先升级到iOS 9)。

2

在iPhone上的Apple watch软件中,进入我的手表>照片>已同步相薄。

3

选择你要同步到Apple watch的相薄。

4

表盘的界面,使用Force Touch功能,用力点按表盘,进入表盘设置界面。

5

左右划动,找到照片表盘,点击表盘下方的“自定”按钮,进入自定义设置。

6

在自定义界面中,滚动圆形表冠Digital Crown进行,轻点选择要做壁纸的照片。

7

全部选好后,按圆形表冠Digital Crown确认两次,第一次是确认对该表盘的自定义设置,第二次是确认使用当前表盘

尽管 Apple Watch 提供了超过 25 款不同设计的原生表盘,但似乎它们仍不能满足用户的需求。如果你想让自己的 iPhone 变得更加与众不同,你只需换壁纸、换手机壳就行了。但在 Apple Watch 上实现个性化外观定制的方法,却寥寥无几。下面我们整理了一些切实可行的方法,能让你的 Apple Watch 立刻极具个性。

照片表盘

将照片、视频、实况照片等作为 Apple Watch 表盘,可能是个性化表盘最简单的实现方式了。如果你刚好有一点设计基础,那么完全可以让你的表盘变得与众不同。

@HALFYUAN- 就以宝可梦经典元素精灵球设计了撞色,把它们添加到 Apple Watch 的照片表盘中,再搭配红白撞色硅胶表带,看起来就像是一支 Apple Watch 宝可梦定制款。除此之外,马里奥、钢铁侠、哈利波特、愤怒的小鸟等都成为他的设计素材,不同的搭配不同的表带,就能轻松玩出无限花样。

在表盘上显示自定义字符

Apple Watch Series 4 独占的 Infogragh 表盘最多可容纳八个复杂功能,其中字母图案就可以允许你输入一些字符显示。如果你想让 Apple Watch 显示  或者其他有趣的字符,都可以用支持显示字母图案的 Infogragh 表盘或者「彩色」表盘。按照以下步骤即可。

拷贝字符。

打开 Watch 应用中的「时钟」-「字母图案」,粘贴字符。注意字母图案仅允许显示 4 个字母。

在 Infograph 或「彩色」表盘中自定义组件来显示字母图案。

如果你一时想不到合适的字符,可以在颜文字、花样文字 、Symbol 等 App 中找找灵感。

动态撞色表盘

今年除了 Apple Watch Series 4 新增了独占表盘外,爱马仕款也有了新款定制表盘,非常吸引眼球。爱马仕的表盘采用双色撞色设计,而且背景色会根据分针的走动而实时移动。就像 macOS Mojave 动态壁纸一样,这款表盘也是动态的,效果非常动人。

为了能让标准款的 Apple Watch 也能用上类似的撞色表盘,@Steve Troughton-Smith 特意开发了一款 watchOS 应用来仿制爱马仕的动态撞色表盘,并有数十种配色。

虽然这款应用没有上架,但是你可以在 GitHub 上下载源代码编译安装。如果你从未写过代码、没有编译过工程也没关系,yves(不是郭老师 )在他的博客中整理了 编译教程。我们在下面简单列示了操作步骤。

准备工作

下载 Xcode,并在「Preference」-「Accounts」中登录开发者账号(可以在 Apple Developer 网站注册申请,不需要付费)。

在 GitHub 下载工程包,并使用 Xcode 打开。

修改工程属性

如果我们直接编译 Steve 的工程,Xcode 会至少报告八个错误,原因就是该工程使用了 Steve 的 developer credentials,我们要将它改为我们自己的。

点击左侧搜索按钮,在搜索框中输入 team,并将 Development Team 一栏修改为我们自己的。随后搜索 highcaffein,并替换为自己的代码,可以随意填写,比如我修改为 ElijahLee。

第一次编译

现在我们可以开始编译过程,在 Xcode 左上角,我们先选择「DesktopShim」-「My Mac」。

然后点击左侧的运行 ▶️ 按钮,过一会,Mac 上会出现 DesktopShimapp 并显示出撞色表盘了。

仿真编译

接下来,我们可以尝试在 Mac 上仿真出一台 iPhone XS 和 Apple Watch Series 4 来验证这个表盘。我们选择 「SpriteKitWatchFace WatchKit App」-「iPhone XS Max + Apple Watch Series 4」。

然后点击左侧的播放按钮,顺利的话,Mac 上会仿真出一台 iPhone XS 和 Apple Watch,你可以通过鼠标滑动和点击来操作它们。在这台 iPhone 主屏幕的最后一页,会出现名为 Duotone 的 App,打开可以看到一个全屏幕的撞色表盘。而在 Apple Watch 上,要过个五六分钟后,才会显示这个 Duotone,打开它也会显示全屏幕的撞色表盘。(注意:我编译的结果出现了表盘 UI 偏移错位的情况,但在 Apple Watch 上显示正常。)

在真机上运行

以上步骤都顺利之后,我们就可以将 iPhone 连接至 Mac,选择「SpriteKitWatchFace」-「你的 iPhone」进行编译(未付费的开发者账号每 7 天内只能在真机上运行 10 次),成功后你的 iPhone 和 Apple Watch 上都会出现 Duotone App。打开就可以显示表盘,我们还可以在 Apple Watch 上旋转数码表冠,修改撞色表盘的配色,非常方便。

这时你应该能明白这款撞色表盘的原理,它实际上是一款 watchOS App,打开后显示一个全屏幕的表盘页面,只要这个应用保持前台常驻,就可以替代原生的表盘。因此我们可以打开 Watch 应用中的「通用」-「唤醒屏幕」-「唤醒屏幕时显示最后使用的应用」-「始终」来确保每次抬腕唤醒 Apple Watch 时,Duotone 都会显示。

再次定制

Steve 还设置了许多表盘样式参数,来为方便我们修改,例如表盘形状、指针样式等等,这在工程的 FaceScenem 中可以修改。比如你可以将其中的

selfuseBackgroundImageOverlay = NO;

selffaceStyle = FaceStyleRound;

selfnumeralStyle = NumeralStyleAll;

selftickmarkStyle = TickmarkStyleAll;

selfmajorTickmarkShape = TickmarkShapeRectangular;

selfminorTickmarkShape = TickmarkShapeRectangular;

selfmajorTickHeight = 6;

selfmajorTickWidth = 2;

selfcolorRegionStyle = ColorRegionStyleDynamicDuo;

selfdateStyle = DateStyleDayDate;

selfdateQuadrant = DateQuadrantRight;

修改为

selfuseBackgroundImageOverlay = YES;

selffaceStyle = FaceStyleRectangular;

selfnumeralStyle = NumeralStyleNone;

selftickmarkStyle = TickmarkStyleNone;

selfmajorTickmarkShape = TickmarkShapeRectangular;

selfminorTickmarkShape = TickmarkShapeRectangular;

selfmajorTickHeight = 6;

selfmajorTickWidth = 2;

selfcolorRegionStyle = ColorRegionStyleDynamicDuo;

selfdateStyle = DateStyleDayDate;

selfdateQuadrant = DateQuadrantTop;

编译后你就会发现,表盘变成了方形,数字变成白色描边而且日期移动到了中间位置。

Think Different 表盘

在 Steve 的工作基础上,知名第三方微博客户端 Maipo 的开发者 @Naituw 也移植了两款 Apple 经典风格的表盘,分别是指针逆时针走动的 Think Different 风格和 System 75 操作系统风格。你也可以在 GitHub 上下载工程在 Apple Watch 上运行。

实际上,在 Steve 的基础上,众多开发者都参与到了自定义 Apple Watch 表盘的队伍中。@Aaron、@David Smith、@Cosmo - Devran Uenal、@SpookyStraws 等或自行设计或从经典手表复刻样式,让自己的 Apple Watch 都用上了独一无二的表盘。

小结

尽管每个 Apple Watch 用户都希望用上更加小众更合口味的表盘,开发者们也显示出了令人惊叹的创意设计,但我认为短时间内 Apple 并不会开放完全自定义 Apple Watch 表盘的功能。正如每推出一款 iPhone 必有新配色的加持护航一样,独占的 watchOS 新表盘也很有可能成为今后新款 Apple Watch 的营销策略之一。此外,设计版权问题、功能实用性等等也会成为 Apple 保持表盘掌控权的重要原因。

不管怎样,Setve 等开发者给我们提供了一种相对便捷的方式来自定义表盘,你完全可以尝试这些与众不同的设计。

一:要解决的问题

我们在尝鲜 JDK15 的时候,相信不少人遇到过 Unsupported majorminor version 490 错误,当时定会茫然不知所措。因为刚开始那会儿,网上与此相关的中文资料还不多,现在好了,网上一找就知道是如何解决,大多会告诉你要使用 JDK 14 重新编译。那么至于为什么,那个 majorminor 究竟为何物呢?这就是本篇来讲的内容,以使未错而先知。

我觉得我是比较幸运的,因为在遇到那个错误之前已研读过《深入 Java 虚拟机》第二版,英文原书名为《Inside the Java Virtual Machine》( Second Edition),看时已知晓 majorminor 藏匿于何处,但没有切身体会,待到与 Unsupported majorminor version 490 真正会面试,正好是给我验证了一个事实。

首先我们要对 Unsupported majorminor version 490 建立的直接感觉是:JDK15 编译出来的类不能在 JVM 14 下运行,必须编译成 JVM 14 下能运行的类。(当然,也许你用的还是 JVM 13 或 JVM 12,那么就要编译成目标 JVM 能认可的类)。这也解决问题的方向。

二:majorminor 栖身于何处

何谓 majorminor,且又居身于何处呢?先感性认识并找到 majorminor 来。

写一个 Java Hello World! 代码,然后用 JDK 15 的编译器编译成,HelloWorldjava

package comunmi;

public class HelloWorld

{

public static void main(String[] args)

{

Systemoutprintln("Hello, World!");

}

}

package comunmi;public class HelloWorld{ public static void main(String[] args) { Systemoutprintln("Hello, World!"); }}

用 JDK 15 的 javac -d HelloWorldjava 编译出来的字节码 HelloWorldclass 用 UltraEdit 打开来的内容如图所示:

从上图中我们看出来了什么是 majorminor version 了,它相当于一个软件的主次版本号,只是在这里是标识的一个 Java Class 的主版本号和次版本号,同时我们看到 minor_version 为 0x0000,major_version 为 0x0031,转换为十制数分别为0 和 49,即 majorminor 就是 490 了。

三:何谓 majorminor 以及何用

Class 文件的第 5-8 字节为 minor_version 和 major_version。Java class 文件格式可能会加入新特性。class 文件格式一旦发生变化,版本号也会随之变化。对于 JVM 来说,版本号确定了特定的 class 文件格式,通常只有给定主版本号和一系列次版本号后,JVM 才能够读取 class 文件。如果 class 文件的版本号超出了 JVM 所能处理的有效范围,JVM 将不会处理该 class 文件。

在 Sun 的 JDK 102 发布版中,JVM 实现支持从 450 到 453 的 class 文件格式。在所有 JDK 11 发布版中的 JVM 都能够支持版本从 450 到 4565535 的 class 文件格式。在 Sun 的 12 版本的 SDK 中,JVM 能够支持从版本 450 到460 的 class 文件格式。

10 或 12 版本的编译器能够产生版本号为 453 的 class 文件。在 Sun 的 12 版本 SDK 中,Javac 编译器默认产生版本号为 453 的 class 文件。但如果在 javac 命令行中指定了 -target 12 标志,12 版本的编译器将产生版本号为 460 的 class 文件。10 或 11 版本的 JVM 上不能运行使用-target 12 标志所产生的 class 文件。

JVM 实现的 第二版中修改了对 class 文件主版本号和次版本号的解释。对于第二版而言,class 文件的主版本号与 Java 平台主发布版的版本号保持一致(例如:在 Java 2 平台发布版上,主版本号从 45 升至 46),次版本号与特定主平台发布版的各个发布版相关。因此,尽管不同的 class 文件格式可以由不同的版本号表示,但版本号不一样并不代表 class 文件格式不同。版本号不同的原因可能只是因为 class 文件由不同发布版本的 java 平台产生,可能 class 文件的格式并没有改变。

上面三段节选自《深入 Java 虚拟机》,啰嗦一堆,JDK 12 开启了 Java 2 的时代,但那个年代仍然离我们很远,我们当中很多少直接跳在 JDK 14 上的,我也差不多,只是项目要求不得不在一段时间里委屈在 JDK 13 上。不过大致我们可以得到的信息就是每个版本的 JDK 编译器编译出的 class 文件中都带有一个版本号,不同的 JVM 能接受一个范围 class 版本号,超出范围则要出错。不过一般都是能向后兼容的,知道 Sun 在做 Solaris 的一句口号吗?保持对先前版本的 100% 二进制兼容性,这也是对客户的投资保护。

四:其他确定 class 的 majorminor version 办法

1)Eclipse 中查看

Eclipse 33 加入的新特征,当某个类没有关联到源代码,打开它会显示比较详细的类信息,当然还未到源码级别了,看下图是打开 20 springjar 中 ClasspathXmlApplicationContextclass 显示的信息

2)命令 javap -verbose

对于编译出的 class 文件用 javap -verbose 能显示出类的 majorminor 版本,见下图:

3) MANIFEST 文件

把 class 打成的 JAR 包中都会有文件 META-INF\MANIFEST,这个文件一般会有编译器的信息,下面列几个包的 META-INF\MANIFEST 文件内容大家看看

·Velocity-15jar 的 META-INFO\MANIFEST 部份内容

Manifest-Version: 10

Ant-Version: Apache Ant 170

Created-By: Apache Ant

Package: orgapachevelocity

Build-Jdk: 142_08

Extension-Name: velocity

我们看到是用 ant 打包,构建用的JDK是 142_08,用 14 编译的类在 14 JVM 中当然能运行。如果那人用 15 的 JDK 来编译,然后用 JDK 14+ANT 来打包就太无聊了。

·20 springjar 的 META-INFO\MANIFEST 部份内容

Manifest-Version: 10

Ant-Version: Apache Ant 165

Created-By: 150_08-b03 (Sun Microsystems Inc)

Implementation-Title: Spring Framework

这下要注意啦,它是用的 JDK 15 来编译的,那么它是否带了 -target 14 或 -target 13 来编译的呢?确实是的,可以查看类的二进制文件,这是最保险的。所在 spring-20jar 也可以在 14 JVM 中加载执行。

·自已一个项目中用 ant 打的 jar 包的 META-INFO\MANIFEST

Manifest-Version: 10

Ant-Version: Apache Ant 170

Created-By: 142-b28 (Sun Microsystems Inc)

用的是 JDK 14 构建打包的。

第一第二种办法能明确知道 majorminor version,而第三种方法应该也没问题,但是碰到变态构建就难说了,比如谁把那个 META-INFO\MANIFEST 打包后换了也未可知。直接查看类的二进制文件的方法可以万分保证,准确无误,就是工具篡改我也认了。

五:编译器比较及症节之所在

现在不妨从 JDK 11 到 JDK 17 编译器编译出的 class 的默认 minormajor version 吧。(又走到 Sun 的网站上翻腾出我从来都没用过的古董来)

JDK 编译器版本 target 参数 十六进制 minormajor 十进制 minormajor

jdk118 不能带 target 参数 00 03 00 2D 453

jdk122 不带(默认为 -target 11) 00 03 00 2D 453

jdk122 -target 12 00 00 00 2E 460

jdk131_19 不带(默认为 -target 11) 00 03 00 2D 453

jdk131_19 -target 13 00 00 00 2F 470

j2sdk142_10 不带(默认为 -target 12) 00 00 00 2E 460

j2sdk142_10 -target 14 00 00 00 30 480

jdk150_11 不带(默认为 -target 15) 00 00 00 31 490

jdk150_11 -target 14 -source 14 00 00 00 30 480

jdk160_01 不带(默认为 -target 16) 00 00 00 32 500

jdk160_01 -target 15 00 00 00 31 490

jdk160_01 -target 14 -source 14 00 00 00 30 480

jdk170 不带(默认为 -target 16) 00 00 00 32 500

jdk170 -target 17 00 00 00 33 510

jdk170 -target 14 -source 14 00 00 00 30 480

Apache Harmony 50M3 不带(默认为 -target 12) 00 00 00 2E 460

Apache Harmony 50M3 -target 14 00 00 00 30 480

上面比较是 Windows 平台下的 JDK 编译器的情况,我们可以此作些总结:

1) -target 11 时 有次版本号,target 为 12 及以后都只用主版本号了,次版本号为 0

2) 从 11 到 14 语言差异比较小,所以 12 到 14 默认的 target 都不是自身相对应版本

3) 15 语法变动很大,所以直接默认 target 就是 15。也因为如此用 15 的 JDK 要生成目标为 14 的代码,光有 -target 14 不够,必须同时带上 -source 14,指定源码的兼容性,16/17 JDk 生成目标为 14 的代码也如此。

4) 16 编译器显得较为激进,默认参数就为 -target 16。因为 16 和 15 的语法无差异,所以用 -target 15 时无需跟着 -source 15。

5) 注意 17 编译的默认 target 为 16

6) 其他第三方的 JDK 生成的 Class 文件格式版本号同对应 Sun 版本 JDK

7) 最后一点最重要的,某个版本的 JVM 能接受 class 文件的最大主版本号不能超过对应 JDK 带相应 target 参数编译出来的 class 文件的版本号。

上面那句话有点长,一口气读过去不是很好理解,举个例子:14 的 JVM 能接受最大的 class 文件的主版本号不能超过用 14 JDK 带参数 -target 14 时编译出的 class 文件的主版本号,也就是 48。

因为 15 JDK 编译时默认 target 为 15,出来的字节码 majorminor version 是 490,所以 14 的 JVM 是无法接受的,只有抛出错误。

那么又为什么从 11 到 12、从 12 到 13 或者从 13 到 14 的 JDK 升级不会发生 Unsupported majorminor version 的错误呢,那是因为 12/13/14 都保持了很好的二进制兼容性,看看 12/13/14 的默认 target 分别为 11/11/12 就知道了,也就是默认情况下14 JDK 编译出的 class 文件在 JVM 12 下都能加载执行,何况于 JVM 13 呢?(当然要去除使用了新版本扩充的 API 的因素)

六:找到问题解决的方法

那么现在如果碰到这种问题该知道如何解决了吧,还会像我所见到有些兄弟那样,去找个 14 的 JDK 下载安装,然后用其重新编译所有的代码吗?其实大可不必如此费神,我们一定还记得 javac 还有个 -target 参数,对啦,可以继续使用 15 JDK,编译时带上参数 -target 14 -source 14 就 OK 啦,不过你一定要对哪些 API 是 15 JDK 加入进来的了如指掌,不能你的 class 文件拿到 JVM 14 下就会 method not found。目标 JVM 是 13 的话,编译选项就用 -target 13 -source 13 了。

相应的如果使用 ant ,它的 javac 任务也可对应的选择 target 和 source

<javac target="14" source="14" />

如果是在开发中,可以肯定的是现在真正算得上是 JAVA IDE 对于工程也都有编译选项设置目标代码的。例如 Eclipse 的项目属性中的 Java Compiler 设置,如图

自已设定编译选项,你会看到选择不同的 compiler compliance level 是,Generated class files compatibility 和 Source compatibility 也在变,你也可以手动调整那两项,手动设置后你就不用很在乎用的什么版本的编译器了,只要求他生成我们希望的字节码就行了,再引申一下就是即使源代码是用 VB 写的,只要能编译成 JVM 能执行的字节码都不打紧。在其他的 IDE 也能找到相应的设置对话框的。

其他时候,你一定要知道当前的 JVM 是什么版本,能接受的字节码主版本号是多少(可对照前面那个表)。获息当前 JVM 版本有两种途径:

第一:如果你是直接用 java 命令在控制台执行程序,可以用 java -version 查看当前的 JVM 版本,然后确定能接受的 class 文件版本

第二:如果是在容器中执行,而不能明确知道会使用哪个 JVM,那么可以在容器中执行的程序中加入代码 SystemgetProperty("javaruntimeversion"); 或 SystemgetProperty("javaclassversion"),获得 JVM 版本和能接受的 class 的版本号。

最后一绝招,如果你不想针对低版本的 JVM 用 target 参数重新编译所有代码;如果你仍然想继续在代码中用新的 API 的话;更有甚者,你还用了 JDK 15 的新特性,譬如泛型、自动拆装箱、枚举等的话,那你用 -target 14 -source 14 就没法编译通过,不得不重新整理代码。那么告诉你最后一招,不需要再从源代码着手,直接转换你所正常编译出的字节码,继续享用那些新的特性,新的 API,那就是:请参考之前的一篇日志:Retrotranslator让你用JDK15的特性写出的代码能在JVM14中运行 ,我就是这么用的,做好测试就不会有问题的。

七:再议一个实际发生的相关问题

这是一个因为拷贝 Tomcat 而产生的 Unsupported majorminor version 490 错误。情景是:我本地安装的是 JDK 15,然后在网上找了一个 EXE 的 Tomcat 安装文件安装了并且可用。后来同事要一个 Tomcat,不想下载或安装,于是根据我以往的经验是把我的 Tomcat 整个目录拷给他应该就行了,结果是拿到他那里浏览 jsp 文件都出现 Unsupported majorminor version 490 错误,可以确定的是他安装的是 14 的 JDK,但我还是有些纳闷,先前对这个问题还颇有信心的我傻眼了。惯性思维是编译好的 class 文件拿到低版本的 JVM 会出现如是异常,可现并没有用已 JDK 15 编译好的类要执行啊。

后来仔细看异常信息,终于发现了 %TOMCAT_HOME%\common\lib\toolsjar 这一眉目,因为 jsp 文件需要依赖它来编译,打来这个 toolsjar 中的一个 class 文件来看看,490,很快我就明白原来这个文件是在我的机器上安装 Tomcat 时由 Tomcat 安装程序从 %JDK15%\lib 目录拷到 Tomcat 的 lib 目录去的,造成在同事机器上编译 JSP 时是 14 的 JVM 配搭着 490 的 toolsjar,那能不出错,于是找来 14 JDK 的 toolsjar 替换了 Tomcat 的就 OK 啦。

八:小结

其实理解 majorminor 就像是我们可以这么想像,同样是微软件的程序,32 位的应用程序不能拿到 16 位系统中执行那样。

如果我们发布前了解到目标 JVM 版本,知道怎么从 java class 文件中看出 majorminor 版本来,就不用等到服务器报出异常才着手去解决,也就能预知到可能发生的问题。

其他时候遇到这个问题应具体解决,总之问题的根由是低版本的 JVM 无法加载高版本的 class 文件造成的,找到高版本的 class 文件处理一下就行了

DABAN RP主题是一个优秀的主题,极致后台体验,无插件,集成会员系统
网站模板库 » ASP.NET中实时图表的实现

0条评论

发表评论

提供最优质的资源集合

立即查看 了解详情