- 浏览: 46097 次
- 性别:
- 来自: 武汉
文章分类
最新评论
-
jeson0725:
Hibernate应用One2Many -
sean2012:
xuanjunqi 写道请问LZ 我该怎么运行嗯 我是小白嗯
...
多线程小游戏 -
xuanjunqi:
请问LZ 我该怎么运行嗯 我是小白嗯
多线程小游戏 -
wzhilin:
...
Android登陆界面 -
where:
nice
多线程小游戏
画图板的实现
项目截图:
这是我学习Java做的第一个小软件,这是一个仿照Windows XP系统下的画图板自制的画板,功能比较简单,这个项目仅仅是我们学习如何使用java.swing、java.even、java.awt、java.io包下的组件,以及对于一个面板框架的布局。
因此,这次给大家分享这个小项目非常简单,希望大家一看就能懂。
一、关键技术点
a)窗体布局
b)组件的添加(略,这个参考api文档中的java.swing下的组件就OK啦,很简单!)
c)工具组件方法的实现——动态多态实现
d)颜色组件方法的实现
e)监听技术——鼠标监听器、动作监听器
f)面板重绘——二维数组与队列存储的比较
g)保存/打开
h)BMP文件格式
二、关键技术点的实现
a)窗体布局
在这个项目中,我运用到了两种窗体布局BorderLayout边框布局和FlowLayout流式布局,下面就简单的介绍一下这两种布局。
BorderLayout边框布局
Up up = new Up(); //菜单栏面板 JMenuBar bar = up.setUp(); //菜单栏对象 Left left = new Left();//工具栏面板容器 Center center = new Center();//绘图区面板容器 Down down = new Down();//颜料区面板容器
首先我创建了这四个面板,这四个面板的创建方法我都把它们封闭在一个类里了,在这里只能看到这四行实现化对象的过程,但从这些对象名可以很容易区分出来,up是菜单栏的面板,它的大小面板中的内容都已经在这个面板类中初始化完成了,其它left/center/down,想必大家都明白。那么最终只需要组装这几个面板就行了。
this.setJMenuBar(bar);//添加菜单栏 this.add(left.setLeft(), java.awt.BorderLayout.WEST); this.add(down.setDown(), java.awt.BorderLayout.SOUTH); this.add(center.setCenter(), java.awt.BorderLayout.CENTER);
FlowLayout流式布局
流式布局就是按行排列,一行放满了就换到下行去,这个布局是在各个面板容器中去实现的,通过上面的项目截图应该就很清楚的明白我是设置的从左对齐,当然也可以设置居中对齐、右对齐;例如:java.awt.FlowLayout f2 = new java.awt.FlowLayout(FlowLayout.LEFT); // 画板流式布局;其中FlowLayout.LEFT这个参数就是控制了对齐方式,我们可以改成居中对齐FlowLayout.CENTER右对齐FlowLayout.RIGHT.
b)组件的添加(略)
c)工具组件方法的实现——动态多态实现
通过Windows XP的画板我们就可以知道,左面板上有许多的工具,有的是选择工具,有的是画图工具,有的是辅助设计工具,其实要实现这些工具都不难,下面我们透过几个简介的画图工具来试试。下面要介绍的这个技术点是在Java面向对象设计语言中,非常具有代表的一项特性,java语言的主要特性有三:封装、继承、多态;
那么简单的描述一下什么是多态:一个事物可呈现出多种形态。那么具体接口的多种不同的实现方式即为多态。下面我们具体到实例中来看吧。
package MyPaint; import java.awt.Color; import java.awt.Graphics; /** * 绘图的方法体组合 */ public abstract class Shapenes { public int x1,y1,x2,y2,type; public Color color; public Shapenes(int x1, int y1, int x2, int y2,int type,Color color) { super(); this.x1 = x1; this.y1 = y1; this.x2 = x2; this.y2 = y2; this.type = type; this.color = color; } public abstract void draw(Graphics peaper); }
这是一个抽象类,其实刚才讲的接口,也就是一个纯抽象类,但抽象类是介于抽象类与普通类之间的数据结构。这里我们可以看到这个抽象类中,有变量的声明,构造方法,还有一个很重要的元素,就是这个抽象方法abstract,何为抽象方法,就是没有实现的方法,在没有定义之前它不是真正存在的;只有当其它类继承了这个抽象类,实现了抽象类中的draw这个抽象方法,这个方法才是真正存在。所有下面我们可以再来构造其它的画图工具来继承这个抽象类,那可能讲了这么多,大家还是不明白我为什么要这样做;其实后面很简单,我就是要用一个对象名去调用不同对象的方法,请看实例:
/** * 创建绘图工具 */ //图形的对象的存储,实现了一个对象可以接受各种不同的 Shapenes shape = new Drawline(x1,y1,x2,y2,0,color); if (Button_name.equals("Line")){ shape = new Drawline(x1,y1,x2,y2,0,color); } else if (Button_name.equals("Rect")) {shape = new DrawRect(x1,y1,x2,y2,1,color);} else if (Button_name.equals("Oval")) {shape = new DrawOval(x1,y1,x2,y2,2,color);} else if (Button_name.equals("fillOval")) {shape = new DrawFOval(x1,y1,x2,y2,3,color);} else if (Button_name.equals("fill3DRect")) {shape = new Draw3DRect(x1,y1,x2,y2,4,color);} else if (Button_name.equals("save")) {System.out.println("save");} else if (Button_name.equals("open")) {System.out.println("open");} shape.draw(peaper); list.add(shape); }
从代码中可以看出,shape可以根据判断的不同,赋予的对象也不同,而最后我都只管调用这一个方法shape.draw(peaper);最终可以画出我想要的各种图形。因此这里就是实现了动态多态技术。如果还不清晰,我向大家举一个例子:抽象类在这里发挥出了巨大的优势,这个类就好像一个铅笔盒,可以放入各种学习用品,铅笔、钢笔、橡皮擦、尺子……当铅笔盒盖上的时候它是一个整体,但取出来的时候又可以变成许多的个体,有效的节约了物理空间;那么在程序中就相当于简化了程序。
d)颜色组件方法的实现
自认为本项目的颜料盒做得最差,但这确实是时间的原因,在这里只是意思一下了,而如果真正仿照Windows XP中的画板,这里的颜料盒应该使用GridLayout网格布局,每一格是一种颜色,这样成和Windows XP中的画板是一模一样的;在这里只能简单的介绍一下,后面再接着去完善。
e)监听技术——鼠标监听器、动作监听器
鼠标监听器:鼠标监听器是用于监听鼠标在中间画布的操作,我们可看看MouseDragGestureRecognizer鼠标监听器中的方法,以下是其中的一部分方法:
public void mouseClicked(MouseEvent e) { }
public void mousePressed(MouseEvent e) { }
public void mouseReleased(MouseEvent e) { }
public void mouseEntered(MouseEvent e) { }
public void mouseExited(MouseEvent e) { }
public void mouseDragged(MouseEvent e) { }
public void mouseMoved(MouseEvent e) { }
这几个方法的功能从这些方法名称就可以明白,因此这里不多解释了,在这些方法中我们可以进行相应的操作,来达到我们想到的操作,例如:MouseEvent e可以获取鼠标的坐标,这样就可确定图形绘制的位置,不同的图形可能需要的坐标点的数量不同,但没关系是可以解决的。
动作监听器
是用来监听工具面板和颜料面板上的按钮动作,当选中某个按钮时在监听器中自动触发相应的动作,当然前提是这些面板上的对象都是在这个监听器的范围之内;所以我们还需要注意无论是鼠标监听器、动作监听器,它只是一个附加功能,最终都需要被对象附加上去。
下面请看一下动作监听器ActionListener接口中的方法:
public void actionPerformed(ActionEvent e);
这个接口中只含有一个方法,并且这个方法还没有实现,那么我们使用这个功能时就必需要先实现这个方法,这个方法如何实现,就和鼠标监听器的方法差不多,那我们要看一下,哪些功能器件要使用到动作监听器,刚才例出了工具面板中的对象,颜料面板中的对象,其实还有一个是菜单栏中的菜单选项。具体实例不看了。
f)面板重绘——二维数组与队列存储的比较
面板重绘是指将缓存中的数据保存到内存中去,如果没有重绘操作你会发现,当你在绘图区绘图之后,只要窗体发现改变,刚刚绘制的图形就消失了,这就是因为刚刚绘制的图形在缓存中,还没有被保存到内存中去,那么重绘就是要来解决这个问题,首先要把画布上的内容使用数据容器,保存到内存中去,当窗体发生变化时,面板重新从内存中调取保存的内容绘制在面板上,这样就达到了面板恢复的效果,但是绘画区的内容暂时保存到内存中去也不能保证万无一失,内存中的内容也不是永久性的,如果计算机发现断电时,内存中的数据会消失,这样依然无法保证我们的成果。这就需要通过文件存取的方式保存到外存中,那样我们就可以永存数据不至于丢失。文件操作在下一节中讲,这里我们暂时来实现,将缓存中的数据保存到内存中去。
刚讲了把画布上的内容使用数据容器,保存到内存中去,那么这个关键技术点就在于这个数据容器上了,如果一个数据容器设计得巧合,那么对于项目操作肯定是有益的。那么想想在Java中数据容器有很多:Set 、List 、Map ……,那哪些是更适合的呢?这里我将针对两种数据容器来使用比较两者的优劣性。
第一:队列
想必大家对队列已经很熟悉了,那么我直接分享队列在保存中的使用,其实在这里队列还涉及到一个很重要的知道点,那就是泛型,如果有不清楚的可以自己去查阅资料;首先我们设想一下,我们可以将每次画上去的图形为一个对象,保存到队列中去,这个对象可能涉及到坐标、颜色、以及是个什么图形。
好,根据提出的这些设想,那么我们就开始实现了,请看实例:
package MyPaint; import java.awt.Color; import java.awt.Graphics; /** * 绘图的方法体组合 */ public abstract class Shapenes { public int x1,y1,x2,y2,type; public Color color; public Shapenes(int x1, int y1, int x2, int y2,int type,Color color) { super(); this.x1 = x1; this.y1 = y1; this.x2 = x2; this.y2 = y2; this.type = type; this.color = color; } public abstract void draw(Graphics peaper); }
这是我们在之前在实现工具的动态多态中所使用的abstract抽象方法,在这里我们又可能用到它了,我们将它作为每个图形的对象,保存到队列中去,从这个类中可以看出来,它保存了对象的两个坐标点、颜色、图形的类型;就是这三个关键元素构成了我们想到的图形,我们既然可以通过这三个元素画出相应的图形,我们保存这三个元素当然能够还原相应的图形。确定这一点后,我们可以继续往下走:
//图形的对象的存储,实现了一个对象可以接受各种不同的 Shapenes shape = new Drawline(x1,y1,x2,y2,0,color); if (Button_name.equals("Line")){ shape = new Drawline(x1,y1,x2,y2,0,color); } else if (Button_name.equals("Rect")) {shape = new DrawRect(x1,y1,x2,y2,1,color);} else if (Button_name.equals("Oval")) {shape = new DrawOval(x1,y1,x2,y2,2,color);} else if (Button_name.equals("fillOval")) {shape = new DrawFOval(x1,y1,x2,y2,3,color);} else if (Button_name.equals("fill3DRect")) {shape = new Draw3DRect(x1,y1,x2,y2,4,color);} else if (Button_name.equals("save")) {System.out.println("save");} else if (Button_name.equals("open")) {System.out.println("open");} shape.draw(peaper); list.add(shape); }
这个方法在是鼠标监听器public void mouseReleased(MouseEvent e)中实现的,在这里我们又可以看到动态多态的优势了,我们绘制不同的图形,最终我们不仅仅是用这一个对象还调用绘画方法,而且用需要保存这一个对象就可以完成对所有不同图形的保存。这个设计模式是太完美了。保存完图形之后,我们还没有完成重点操作,就是实现面板的重绘,要将这些保存的数据与重绘方法结合起来,让这个数据可以随着重绘操作实现更新面板。
这个重绘操作是在中间面板中来完成的,我们要把保存在队列中的数据传到这个面板类中来,在JPanel中有一个重绘方法,我们需要重写这个方法,请看实例:
class MyJPanel extends JPanel { public void paint(Graphics g){ super.paint(g); ListSave<Shapenes> list = mouselisterner.list;///调用子类的变量,需要将mouselisterner定义成子类才行 for(int i=0;i<list.size();i++){ Shapenes shape = (Shapenes) list.get(i); ///新定义一个变量来接收队列中的元素 shape.draw(g); } } }
这里我们定义了一个MyJPanel内部类,那这个类继承了javax.swing.JPanel这个类,重写了public void paint(Graphics g)这个方法,这个方法正式我们要用到的重绘方法,先要调用super.paint(g);父类中的绘图方法,把之前的内容也绘制出来,不然之后绘制的图形会刷新面板使得图形无法显示。在调用完父类中的绘图方法之后,再把队列中的数据绘制上去,这样就实现了重绘操作,之后无论你是“最小化”、“移动窗体”操作都不会对面板中的图形有影响。
第二:二维数组
看完二维数组的保存方法之后,你也许会惊讶二维数组的方便。可以分析一下,我们每幅图画都可以看成是一个矩形,那么矩形岂不是有长和宽,那么在这个矩形区域由许多的像素点组成,而在计算机中每个像素点是可以用一个Int数据类型来表示,这样我们利用图片的分辨率还定义数组,数组内的元素都是存在了像素点的颜色Int值。请看代码实例:
/** * 将图像存入数组中 */ // 创建机器人来截取图像 try { // 创建机器人对象 Robot robot = new Robot(); // 获取画布对象 JPanel center_peaper = (JPanel) e.getSource(); // 获取画布的位置 Point point = center_peaper.getLocationOnScreen(); // 获取画面的高度宽度 Dimension dimension = center_peaper.getPreferredSize(); // 设置截图的位置、高、宽 Rectangle screenRect = new Rectangle(point, dimension); // 初始化二维数组 saveImage = new int[dimension.height][dimension.width]; imageR = new int[dimension.height][dimension.width]; imageG = new int[dimension.height][dimension.width]; imageB = new int[dimension.height][dimension.width]; // 机器人开始截图 BufferedImage bufferedImage = robot.createScreenCapture(screenRect); // 将图像数据存入二维数组中 for (int i = dimension.height - 1; i >= 0; i--) for (int j = 0; j < dimension.width; j++) { // 自定义文件格式保存 int image = bufferedImage.getRGB(j, i); saveImage[i][j] = image; filetype = 1; } }
思路很清晰了,通过代码实例来看,我们所做的无非就是把画板上的图像截取下来,按照图像的分辨率实例化了一个二维数组,用这个数组来保存截图每个点上的颜色值;但这里还是有几个重要的技术点,一个是Robot机器人类的使用,这个不难可以参考API文档,另外一个就是这个图像数据存入二维数组时,要注意图像的长度与二维数组的下标相对应,如果对应错了图像是会发现错误的。通过二维数组保存之外就可以传入重绘方法中去,让重绘方法调用数组中的数据进行重绘。操作就是这么简单,具体Robot的操作在上面的代码中都体现出来了,希望大家一看就懂!通过使用这两种数据容器来保存数据,想必大家会有青睐的一种,无论哪一种,我觉得都有它的优劣性,具体也不分析了,总之学会一种方式就很好了!
g)保存/打开
刚刚我们实现了重绘,那么这个环节我们把内存中的数据再写到外存中去,进行永久的保存,我们依然可以选择其中一种数据容器来进行保存,那么我们要怎么要写入文件呢?拿这个队列为例:
public void actionPerformed(ActionEvent e) { String command = e.getActionCommand(); /** * 文件保存操作 */ if (command.equals("save")) { // 文件保存对象 FileSave filesave = new FileSave(); try { /** * 将队列保存到文件中去 */ filesave.output(lis.list); } catch (Exception e1) { e1.printStackTrace(); } } /** * 文件打开操作 */ else if (command.equals("open")) { // 文件打开对象 FileOpen fileopen = new FileOpen(); try { /** * 将文件从文件中读到列队中来 */ lis.list = fileopen.input(); center_peaper.updateUI(); } catch (Exception e1) { e1.printStackTrace(); } } }
这个方法是在动作监听器中实现的,我们要在单击了“保存”或“打开”这个选项时,我们才会触发相应的动作,这个时候如果单击了“保存”,那么我们首先要创建一个文件,文件名由用户自定义,如果文件创建成功则调用这个文件保存output方法:
public void output(ListSave<Shapenes> list) throws Exception{ //文件输出流 FileOutputStream outputfile = new FileOutputStream("g:\\ss\\savepaint.txt"); //原始数据输出流 DataOutputStream outstream = new DataOutputStream(outputfile); //写入图形对象的个数,即文件头 outstream.write(list.size()); for(int i=0;i<list.size();i++){ Shapenes shape = list.get(i); outstream.writeInt(shape.x1); outstream.writeInt(shape.y1); outstream.writeInt(shape.x2); outstream.writeInt(shape.y2); outstream.writeInt(shape.type); outstream.writeInt(shape.color.getRGB()); } //强制输出 outstream.flush(); //关闭文件输出流 outputfile.close(); }
通过这个过程,我们就可以把我们绘制的作品写入到文件中去了,那么打开操作就是保存互逆的一个过程;如果保存实现了,打开就没必要再多分享了。写入文件后要记得flush一下,close一下。
h)BMP文件格式
其实在这个项目中,还实现了BMP文件格式的解析,在项目截图就可看到绘图板上的那幅图像是BMP的一张图片,BMP是Windows中自带的一种图像文件格式,这种文件格式在网上已经被公开,只要知道这种文件的格式就很好读取出来,显示在我们自制的绘画板上,同样我们也可将我们绘制的图像保存成为BMP格式,这样我们的作品就要以与Windows直接交互使用。
BMP的文件格式在这里不去分析了,只是将我所写的代码分享如下!
public class ButtonListen implements ActionListener { private int[][] saveImage; public static int[][] imageR; public static int[][] imageG; public static int[][] imageB; public static int image_width; public static int image_heigh; public void actionPerformed(ActionEvent e) { /** * 画板内容的保存 */ if (e.getActionCommand().equals("save")) { // 创建保存对话框 JFileChooser filechooser = new JFileChooser(); int num = filechooser.showSaveDialog(null); if (num == 0) { String path = filechooser.getSelectedFile().getAbsolutePath(); ButtonListen file = new ButtonListen(); try { file.savefile(path); } catch (Exception e1) { // TODO Auto-generated catch block e1.printStackTrace(); } } } else if (e.getActionCommand().equals("open")) { // 创建打开对话框 JFileChooser filechooser = new JFileChooser(); int num = filechooser.showOpenDialog(null); if (num == 0) { String path = filechooser.getSelectedFile().getAbsolutePath(); ButtonListen file = new ButtonListen(); try { file.openfile(path); } catch (Exception e1) { // TODO Auto-generated catch block e1.printStackTrace(); } } } } private void openfile(String path) throws Exception{ // TODO Auto-generated method stub //获取文件名 File filename =new File(path); String name = filename.getName().toLowerCase(); String key = ".bmp"; // 创建文件输入流 FileInputStream infile = new FileInputStream(path); // 创建原始数据输入流 DataInputStream instream = new DataInputStream(infile); if(name.indexOf(key)!=-1){///用BMP格式打开 //读入BMP文件头,读取14个字节 int fileheadsize = 14; byte filehead [] = new byte[fileheadsize]; instream.read(filehead, 0, fileheadsize); //读入位图信息头,读取40个字节 int filenewssize = 40; byte filenews [] = new byte[filenewssize]; instream.read(filenews, 0, filenewssize); //从位图信息头中获取重要数据 image_width = ChangeInt(filenews,7); image_heigh = ChangeInt(filenews,11); int image_bit = (((int)filenews[15]&0xff)<<8)|(int)filenews[14]&0xff; //图像的色深位数 int image_size =ChangeInt(filenews,23);//源图的大小 //读取位图中图像的数据 int skip_width = 0; if(!(image_width*3%4==0)){ skip_width = 4-image_width*3%4; }///后面补0的情况处理 //装载RGB颜色的数据数组 imageR = new int [image_heigh][image_width]; imageG = new int [image_heigh][image_width]; imageB = new int [image_heigh][image_width]; //按行读取,H,W倒着读 for(int h=image_heigh-1;h>=0;h--) for(int w=0;w<image_width;w++) { int blue = instream.read(); int green = instream.read(); int red = instream.read(); imageB[h][w] = blue; imageG[h][w] = green; imageR[h][w] = red; if(w == 0){ System.out.println(instream.skipBytes(skip_width)); } } infile.close(); }else {////用自定义格式打开 int h = instream.readInt(); int w = instream.readInt(); saveImage = new int[h][w]; for (int i = 0; i <h; i++) for (int j = 0; j <w; j++) { saveImage[i][j] = instream.readInt(); } infile.close(); //将读取到的数据传入重绘数组 Mouse_Lis.saveImage = saveImage;} }
三、关键技术点的总结
这个项目的主要技术点已经分享完了,最后还做一个小小的总结,这个项目如果有时间我会继续改进;通过这次项目的演练,我对于Swing、Awt下的组件的使用都有了成熟的经验,组件更换显示图标,组件的分布,组件的监听都有了比较感性的认识,尤其是对于对象与类的区别,封装、继承、多态等等这些基础的知识点。但同时也知道对于一个桌面应用程序来说,要做好做完善是需要不断的维护和更新的,这也要求我们自己要不断的进步和学习,这样才能开发出更优秀的软件。
相关推荐
Umi-OCR-main.zip
基于springboot+Web的毕业设计选题系统源码数据库文档.zip
基于springboot校外兼职教师考勤管理系统源码数据库文档.zip
58商铺全新UI试客试用平台网站源码
基于springboot大学生就业信息管理系统源码数据库文档.zip
基于SpringBoot的口腔诊所系统源码数据库文档.zip
数据存放网盘,txt文件内包含下载链接及提取码,永久有效。失效会第一时间进行补充。样例数据及详细介绍参见文章:https://blog.csdn.net/T0620514/article/details/143956923
3-240P2162218.zip
网络安全 基于Qt创建的Linux系统下的浏览器.zip
C++ 类和对象:多态-练习题目2(制作咖啡和茶叶)
基于springboot+J2EE在线项目管理与任务分配中的应用源码数据库文档.zip
简介本项目提供了一个在51单片机上运行的简单操作系统,旨在帮助学习者深入理解操作系统的基本原理和任务调度机制。该操作系统通过汇编和C语言编写,实现了任务调度、中断处理等核心功能,并提供了详细的源代码和注释,方便学习和实践。
本文将深度卷积神经网络(CNN)设计实现一个复杂结构的生成模型,旨在通过多阶段的编码器-解码器结构,能够有效地将灰度图像转换为彩色图像。最后,本文将实现一个简单的Web应用,用户可以通过上传灰度图像,应用会使用预训练的Caffe模型对其进行颜色化,并将结果返回给用户。 1.模型设计:模型由多个卷积层、ReLU激活函数和批归一化层组成,通过前向传播函数将输入的灰度图像(L通道)转换为彩色图像(ab通道)。如果指定了 pretrained=True,则会自动下载并加载预训练的模型权重。 2. 系统通过Flask框架提供了一个Web应用,用户可以上传灰度图像,系统会自动将其转换为彩色图像,并在网页上显示结果。整个过程包括文件验证、图像处理、颜色化预测和结果展示,具有较高的实用性和用户体验。
一个JAVA图形化的、联网的五子棋游戏.zip javaweb
KWDB 是一款面向 【AIoT 场景】的【分布式多模数据库】,支持在同一实例同时建立时序库和关系库并融合处理多模数据,具备千万级设备接入、百万级数据秒级写入、亿级数据秒级读取等时序数据高效处理能力,具有稳定安全、高可用、易运维等特点。
页面数量:7页 网页主题:网站模板、酒店网站模板、官方网站模板 网页页面:首页、关于我们、相关服务、服务详情、在线博客、博客详情、在线留言 页面实现元素:加载动画、滚动加载、主题切换、导航栏 、轮播图、图文列表、图片切换、 文字列表、 按钮悬停、图片悬停、表单 实现技术:HTML、CSS 、JQuery 源码样式及js文件均分开存放,所有内容仅供初学者学习参考
内容概要:本文档提供了详细的 Neo4j 安装与配置指南,涵盖 Windows、Linux 和 Mac 系统的安装步骤。具体包括下载、安装、启动服务、修改配置文件(如端口配置、远程访问和内存限制)、设置管理员密码以及基本的 Cypher 查询语言使用方法。同时,还提供了一些常见问题及其解决方案。 适合人群:数据库管理员、软件开发人员、系统管理员。 使用场景及目标:①帮助初学者快速掌握 Neo4j 的安装与配置;②适用于需要搭建和使用图数据库的项目;③为已有用户解决常见问题。 其他说明:本文档不仅包含了基础的安装和配置流程,还提供了实际操作中可能遇到的问题及其解决方法,有助于提高使用者的实际操作能力。
基于SpringBoot+Vue的软件产品展示销售系统源码数据库文档.zip
《书戴嵩画牛》教学课件.pptx
20届智能车 【项目资源】:包含前端、后端、移动开发、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源,毕业设计等各种技术项目的源码。包括C++、Java、python、web、C#、EDA等项目的源码。 【适用人群】:适用于希望学习不同技术领域的初学者或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。