文档章节

基于VTK的MFC应用程序开发

天蚕宝衣
 天蚕宝衣
发布于 2017/05/06 10:58
字数 3936
阅读 46
收藏 0
点赞 0
评论 0

之前介绍了基于 VTK 的单文档应用程序开发,并以图像重采样为例,实现了一个简单的图 像重采样的应用程序。 对于多文档应用程序, 与单文档应用程序基本一致, 这里就不再讲述。 对话框应用程序是 MFC 应用程序中一个使用非常广泛的框架,本节就以医学图像可视化中 常用的四视图框架程序的实现为例,讲述基于VTK的对话框应用程序开发。

1. 利用VS和CMake建立一个空的MFC对话框程序框架。

利用VS创建一个MFC对话框工程vtkDialog,删除其中的工程文件,完成CMakeLists.txt文件,并添加相应的代码文件和链接VTK动态库,利用CMake配置完毕后,打开生成的工程文件vtkDialog.sln,编译执行,即可得到一个空的对话框程序。其中CvtkDialogDlg为该程序的主对话框类。

2. 设计用户界面,添加相应的控件。

本程序需要实现的功能有

(1)图像读取和管理;

(2)图像切分和浏览。

一个常见的医学图像可视化程序,包括四个视图,横断面视图,矢状面视图,冠状面视图和三维视图。因此,基于以上设计,我们添加一个树控件,MFC中对应的控件类为CTreeCtrl。树控件是最常用的文件管理控件,能够方便的对文件进行层次化组织和管理。四视图的实现则需要四个控件,这里我们选择CStatic控件,将其添加至对话框窗口中。添加完毕后,为控件生成相应的Control类型的变量。 按照上述设计, 需要在CStatic中显示图像。 这就需要对CStatic类继续扩展, 使其支持VTK可视化管线。一个可行的方法是,设计一个CStatic类的子类,并在该子类中实现VTK可视化管线和处理。

3. 实现VTK图像可视化控件。

3.1 首先添加一个MFC类CvtkView

其基类选择为CStatic,并添加至CMakeLists.txt文件中进行管理。

3.2 重载CvtkView类PreSubclassWindow()函数和OnPaint()函数

PreSubclassWindow()函数负责创建VTK可视化管线,OnPaint()函数负责客户区内场景渲染。

3.3 建立VTK可视化管线

VTK可视化管线在第二章中已经介绍过,其中最主要包含:

vtkAcor,vtkRenderer,vtkRenderWindow,vtkRenderWindowInteractor四个部分。

当然根据需要还可以设置vtkRenderWindowInteractorStyle,以及光照,材质,颜色等。在CvtkView类头文件中定义相关对象,并在PreSubclassWindow函数中实例化和构建可视化管线,代码如下。

void CvtkView::PreSubclassWindow() 
{ 
    // TODO: Add your specialized code here and/or call the base class  
    CRect rect;
    GetClientRect(rect);

    // 创建一个渲染器对象。
    m_Renderer = vtkSmartPointer<vtkRenderer>::New();
    
    // 创建一个渲染窗口对象。
    m_RendererWindow = vtkSmartPointer::New();
    m_RenderWindow->SetParentId(this->m_hWnd); 
    m_RenderWindow->SetSize(rect.Width(), rect.Height());
    m_RenderWindow->AddRenderer(m_Renderer); 

    if (m_RenderWindow->GetInteractor() == NULL) 
    { 
        vtkSmartPointer<vtkRenderWindowInteractor> RenderWindowInteractor = 
            vtkSmartPointer<vtkRenderWindowInteractor>::New(); 
        RenderWindowInteractor->SetRenderWindow(m_RenderWindow); 
        RenderWindowInteractor->Initialize(); 

    }

    m_RendererWindow->Start();
    CStatic::PreSubclassWindow(); 
} 

相信通过前面的学习,这里建立可视化管线的流程已经比较熟悉了。需要注意的是,vtkRenderWindow需要通过函数vtkRenderWindow::SetParentId()来建立与控件本身的关联,

这样才能将m_RenderWindow中的渲染内容在控件的窗口上进行显示;

vtkRenderWindow::SetSize()函数则是设置渲染窗口与当前控件客户区保持大小一致。大家可能会有疑问,怎么没有vtkActor?正常的一个可视化管线中,vtkActor表示需要进行渲染或者绘制的对象。这里需要渲染的对象是图像,而与以前不同的是,没有直接去定义一个图像vtkActor。至于原因暂且不管,随着该类功能的逐步完善,我们再详细说明。VTK渲染管线建立完毕后,在OnPaint()函数中调用vtkRenderWindow类的Render()函数来实现渲染。到这里一个基本的VTK显示控件已经实现,在设计界面时,通过MFC自动添加的四个视图变量类型默认为CStatic。由于CvtkView是继承自CStatic,因此我们可以直接将主对话框类CvtkDialogDlg头文件中定义的四个变量类型修改为CvtkView。然后编译运行程序,是不是已经出现了一个四视图的原型了(如下图所示)?由于没有添加任何的渲染对象,因此四个视图均为空的黑色窗口。

3.4 交互式图像切分

该控件需要实现两个基本功能:一是交互式图像切分;二是切片图像提取。

第一个功能,采用vtkResliceCursorWidget和vtkResliceCursor类。

通常两个类同时使用,每个vtkResliceCursorWidget对象中需要定义相应的vtkResliceCursor对象。

vtkResliceCursorWidget通过定义的“十”字坐标轴,提供用户方便的切分和交互方式,支持坐标轴的旋转和平移;当坐标系发生改变时即调用vtkResliceCursor来进行图像切分并进行更新到vtkRenderer对象中。

第二个功能采用vtkImagePlaneWidget实现。该类内部定义了一个vtkImageReslice对象,

利用vtkResliceCursor类中定义的切分平面来切分图像,在其内部通过纹理映射来绘制到一个平面上,并在用户指定的vtkRenderer类进行显示。另外前面在定义可视化管线时,我们并没有定义相关的vtkActor。这主要是因为在视图显示图像时,都是通过相关的widget(组件)来实现, 我们只需要为widget对象设置相应的vtkRenderer类即可,在其内部会自动生成相应的vtkActor类的对象。根据以上分析,在头文件中定义相关对象,并在PreSubclassWindow()函数中进行实例化。

vtkSmartPointer<vtkImagePlaneWidget>                        m_ImagePlaneWidget; 
vtkSmartPointer<vtkResliceCursorWidget>                     m_ResliceCursorWidget; 
vtkSmartPointer<vtkResliceCursor>                           m_ResliceCursor;
vtkSmartPointer<vtkResliceCursorThickLineRepresentation>    m_ResliceCursorRep;

在实例化时需要注意,该视图类在默认情况下渲染的是vtkResliceCursorWidget对象的输出,因此需要为vtkResliceCursorWidget对象指定相应的vtkRenderer类的对象

m_ResliceCursorWidget->SetInteractor(m_RenderWindow->GetInteractor()); 
m_ResliceCursorWidget->SetDefaultRenderer(m_Renderer);

这样在vtkResliceCursorWidget类的对象内部会将切片图像转化为

一个vtkActor类的对象并添加指定的vtkRenderer类的对象中进行显示渲染;

而vtkImagePlaneWidget中也有一个重要的函数

vtkImagePlaneWidget::SetDefaultRenderer(vtkRenderer *)用于设置相应的vtkRenderer来显示切片。根据本程序的设计,vtkImagePlaneWidget产生的切片需要在三维场景中显示,因此这里并没有调用。也就是说,在默认情况下,本控件类只显示二维切片图像。

3.5 添加图像设置函数初始化图像切分对象

控件类需要从外部设置相应的处理图像,因此提供一个接口函数来供外部调用。在3.4中仅仅定义和创建了交互式图像切分对象, 并没有设置相应的输入数据, 因此每次有新的数据传入时,需要为其进行初始化。

void CvtkView::SetImageData(vtkSmartPointer<vtkImageData> ImageData)
{ 
    if (m_ImageData == NULL) 
        return; 
    m_ImageData = ImageData; 
    SetupReslice(); 
} 
void CvtkView::SetupReslice() 
{ 
    if (ImageData == NULL ) 
        return;

    int dims[3]; 
    m_ImageData->GetDimensions(dims); 

    //////////////////////////////////////////////////////////////////////////  
    m_ImagePlaneWidget->SetInput(m_ImageData);   
    m_ImagePlaneWidget->SetPlaneOrientation(m_Direction);   
    m_ImagePlaneWidget->SetSliceIndex(dims[m_Direction]/2);   
    m_ImagePlaneWidget->On();   
    m_ImagePlaneWidget->InteractionOn();   
      
    //////////////////////////////////////////////////////////////////////////  
    m_ResliceCursor->SetCenter(m_ImageData->GetCenter());   
    m_ResliceCursor->SetImage(m_ImageData);   
    m_ResliceCursor->SetThickMode(0);   
  
    m_ResliceCursorRep->GetResliceCursorActor()->   
        GetCursorAlgorithm()->SetResliceCursor(m_ResliceCursor);  
    m_ResliceCursorRep->GetResliceCursorActor()->   
        GetCursorAlgorithm()->SetReslicePlaneNormal(m_Direction);  
  
    m_ResliceCursorWidget->SetEnabled(1);   
    m_Renderer->ResetCamera();   
      
    //////////////////////////////////////////////////////////////////////////  
    double range[2];   
    m_ImageData->GetScalarRange(range);   
    m_ResliceCursorWidget->GetResliceCursorRepresentation()->  
        SetWindowLevel(range[1]-range[0], (range[0]+range[1])/2.0);   
    m_ImagePlaneWidget->SetWindowLevel(range[1]-range[0], (range[0]+range[1])/2.0);   
}  

SetupReslice()函数中首先对vtkImagePlaneWidget类的对象进行初始化,

vtkImagePlaneWidget::SetInput()函数设置输入图像;

vtkImagePlaneWidget::SetPlaneOrientation()函数设置切片的方向;

vtkImagePlaneWidget::SetSliceIndex()函数设置当前方向上默认的层号。

注意先后顺序:

一定要在设置完输入图像和相应的交互对象后,再开启vtkImagePlaneWidget,

相应的函数为:

vtkImagePlaneWidget::On();

vtkImagePlaneWidget::InteractionOn();

然后设置vtkResliceCursor类的对象,vtkResliceCursor::SetInput()函数设置输入图像

vtkResliceCursor::SetCenter()函数设置默认的切分中心点

vtkResliceCursor::SetThickMode()函数设置切分模式

SetThickMode(int)函数当参数为0时,每次切分得到一个单层的图像切片;而当参数为1时开启厚度模式, 可以通过SetThickness()来设置切片厚度,即得到的是一个多层的厚度图像。这里我们关闭厚度模式。 m_ResliceCursorRep是一个vtkResliceCursorThickLineRepresentation对象,即在图像切分时屏幕上显示的“十”字坐标轴,利用该对象设置其关联的vtkResliceCursor对象和设置切分的方向。

SetDefaultRenderer()用于设置显示切分结果所需要的vtkRenderer。

我们这里设置为类成员变量m_Renderer,即每次鼠标进行切分的结果在当前窗口定义的可视化管线中进行显示。同样,在设置vtkResliceCursor对象的输入图像后,

开启vtkResliceCursorWidget对象:

vtkResliceCursorWidget::SetEnabled();

m_Direction为方向标志,取值分别为0,1和2,分别代表X轴,Y轴和Z轴方向,可以通过设置不同的方向值,来实现横断面视图、矢状面视图、冠状面视图。

这样一个具有图像切分功能的控件已经完成,该控件支持用户设置切片方向和图像输入,运行时在每个视图中根据用户设置的方向显示相应的十字坐标轴,用户可以拖动该十字来进行交互。当然,我们还没有实现视图之间的同步。

4. 完善CvtkDialogDlg类

4.1 四视图初始化

首先在CvtkDialogDlg类的初始化函数OnInitDialog()函数中初始化四个视图控件类对象:

void CvtkView::SetImageData(vtkSmartPointer<vtkImageData> ImageData)  
{  
    if (ImageData == NULL) 
        return;  
      
    m_ImageData = ImageData;  
    SetupReslice();  
}  
void CvtkView::SetupReslice()  
{  
    if (m_ImageData == NULL) 
        return;  
    int dims[3];  
    m_ImageData->GetDimensions(dims);  
  
    //////////////////////////////////////////////////////////////////////////  
    m_ImagePlaneWidget->SetInput(m_ImageData);   
    m_ImagePlaneWidget->SetPlaneOrientation(m_Direction);   
    m_ImagePlaneWidget->SetSliceIndex(dims[m_Direction]/2);   
    m_ImagePlaneWidget->On();   
    m_ImagePlaneWidget->InteractionOn();   
      
    //////////////////////////////////////////////////////////////////////////  
    m_ResliceCursor->SetCenter(m_ImageData->GetCenter());   
    m_ResliceCursor->SetImage(m_ImageData);   
    m_ResliceCursor->SetThickMode(0);   
  
    m_ResliceCursorRep->GetResliceCursorActor()->   
        GetCursorAlgorithm()->SetResliceCursor(m_ResliceCursor);  
    m_ResliceCursorRep->GetResliceCursorActor()->   
        GetCursorAlgorithm()->SetReslicePlaneNormal(m_Direction);  
  
    m_ResliceCursorWidget->SetEnabled(1);   
    m_Renderer->ResetCamera();   
      
    //////////////////////////////////////////////////////////////////////////  
    double range[2];   
    m_ImageData->GetScalarRange(range);   
    m_ResliceCursorWidget->GetResliceCursorRepresentation()->  
        SetWindowLevel(range[1]-range[0], (range[0]+range[1])/2.0);   
    m_ImagePlaneWidget->SetWindowLevel(range[1]-range[0], (range[0]+range[1])/2.0);   
}  

从代码可以看出,我们为每个视图指定了切片的方向,然后为每个视图的vtkImagePlaneWidget类的对象指定相应的交互对象和绘制对象vtkRenderer。在前面我们也提到过,vtkImagePlaneWidget需要为其指定相应的vtkRenderer类的对象,用来显示图像切片。这里vtkImagePlaneWidget的结果需要在三维视图中进行显示,因此通过SetDefaultRenderer()函数将三维视图的vtkRenderer对象设置到每个二维视图中的 vtkImagePlaneWidget类的对象中,这样即可在三维视图中显示三个方向的切片图像。

另外需要注意的是,

m_SagittalView.SetResliceCursor(m_AxialView.GetResliceCursor()); m_CoronalView.SetResliceCursor(m_AxialView.GetResliceCursor());

这里分别将m_SagittalView类m_CoronalView类中的vtkResliceCursor类的对象都设置为

m_AxialView类vtkResliceCursor类的对象,即在三个二维视图中统一使用一个vtkResliceCursor类的对象。

我们知道在二维视图类中定义了vtkResliceCursorWidget类的对象,

该对象通过用户交互,利用vtkResliceCursor类的对象来实现图像的交互式切分。但是在图像切分的同时,三个方向应该保持同步, 即当一个图像切分中心发生改变时, 其他两个方向的视图应该及时进行更新来保持同步。因此,这里将三个视图中统一使用一个共同的vtkResliceCursor类的对象,便于实现视图的同步。

4.2 四视图同步

由于三个二维视图使用同一个vtkResliceCursor类的对象,因此当任意一个vtkResliceCursor对象的图像切分参数(即切分平面参数:中心点法向)发生改变时,其他视图的图像切分参数同样会发生改变。不过由于在视图内部并不能直接检测参数变化,我们需要通过VTK的Observer-Command模式来监听参数改变的消息,并进行相应的处理。切分参数的改变, 是通过用户拖动或者旋转视图的“十”字坐标轴来实现的,

此时vtkResliceCursorWidget内部会产生

一个消息vtkResliceCursorWidget::ResliceAxesChangedEvent,我们只需要监听该消息即可。

因此,我们需要定义一个vtkCommand类,为vtkResliceCursorWidget::ResliceAxesChangedEvent实现相应的处理操作。此外,当用户改变图像切分的坐标轴时(旋转坐标轴或者平移坐标系),图像切分平面会产生相应的改变,如果将新的切分平面更新到二维视图的vtkImagePlaneWidget对象中,即可实现三维视图的同步更新操作。基于以上设计实现一个vtkCommand子类,

来监听vtkResliceCursorWidget::ResliceAxesChangedEvent消息,并实现相应的更新操作。

class vtkResliceCursorCallback : public vtkCommand   
{   
public:   
    static vtkResliceCursorCallback *New()   
    { return new vtkResliceCursorCallback; }   
  
    void Execute(vtkObject *caller, unsigned long /*ev*/,   
        void *callData)   
    {   
        vtkResliceCursorWidget *rcw = dynamic_cast<vtkResliceCursorWidget *>(caller);   
        if (rcw)   
        {    
            for (int i = 0; i < 3; i++)   
            {   
                vtkPlaneSource *ps = static_cast<vtkPlaneSource *>(   
                    view[i]->GetImagePlaneWidget()->GetPolyDataAlgorithm());   
  
                ps->SetOrigin(view[i]->GetResliceCursorWidget()->  
                    GetResliceCursorRepresentation()->GetPlaneSource()->GetOrigin());  
                ps->SetPoint1(view[i]->GetResliceCursorWidget()->  
                    GetResliceCursorRepresentation()->GetPlaneSource()->GetPoint1());  
                ps->SetPoint2(view[i]->GetResliceCursorWidget()->  
                    GetResliceCursorRepresentation()->GetPlaneSource()->GetPoint2());  
  
                view[i]->GetImagePlaneWidget()->UpdatePlacement();   
                view[i]->Render();  
            }   
            view[3]->Render();  
        }   
          
    }   
  
    vtkResliceCursorCallback() {}   
    CvtkView *view[4];  
};  

每当监听到vtkResliceCursorWidget::ResliceAxesChangedEvent消息后,

即可执行vtkResliceCursorCallback::Execute()函数来实现视图同步与刷新操作。 该函数更新三个视图中

vtkImagePlaneWidget类的对象的切分平面参数,即利用每个视图中

vtkResliceCursorThickLineRepresentation::GetPlaneResource()

获取当前方向的纹理映射平面对象vtkPlaneSource,并获取平面的原点,以及定义平面内坐标轴的两个点,

vtkPlaneSource::GetOrigin(), vtkPlaneSource::GetPoint1();

vtkPlaneSource::GetPoint2();

将三个点坐标设置到到vtkImagePlaneWidget的切分平面对象中,

通过调用函数vtkImagePlaneWidget::UpdatePlacement()

更新切分平面的空间位置和姿态,从而得到新的图像切面,最后刷新四个视图完成四视图的同步与更新。 定义完该类后,在初始化函数BOOL CvtkDialogDlg::OnInitDialog()中,定义该类对象并设置相应的监听消息实现视图同步。

// 创建vtk切片重组光标回调类的对象。
vtkSmartPointer<vtkResliceCursorCallback> cbk =   
    vtkSmartPointer<vtkResliceCursorCallback>::New();   
  
cbk->view[0] = &m_AxialView;  
m_AxialView.GetResliceCursorWidget()->AddObserver(   
    vtkResliceCursorWidget::ResliceAxesChangedEvent, cbk);   
  
cbk->view[1] = &m_SagittalView;  
m_SagittalView.GetResliceCursorWidget()->AddObserver(   
    vtkResliceCursorWidget::ResliceAxesChangedEvent, cbk);  
  
cbk->view[2] = &m_CoronalView;  
m_CoronalView.GetResliceCursorWidget()->AddObserver(   
    vtkResliceCursorWidget::ResliceAxesChangedEvent, cbk);  
  
cbk->view[3]= &m_3DView;

4.3 数据管理

采用树控件来管理图像文件,为其响应右键单击消息和左键单击消息。右键单击消息响应树控件的根节点的右键菜单功能,主要定义了图像读取函数,图像保存,图像清空等功能。当图像读入后,即将其设置到三个视图中,并调用相应的CvtkView::Render()函数来更新视图(CvtkView 定义的一个视图刷新函数)。树结点的左键按下消息,实现图像的切换;每次点击一个图像节点,即将当前的图像更新至四视图中。至此,一个完整的四视图图像显示Demo已经完成。该Demo中能够实现横断面,矢状面和冠状面三个方向的二维切片显示,以及三个切片的三维显示;实现了四视图的同步显示, 即一个图像中的切分平面发生变化时,其他视图会同步进行更新。在实现过程中,基于两个VTK Widget类进行实现,分别是:

vtkResliceCursorWidget(vtk切片重组光标部件)和vtkImagePlaneWidget(vtk图像平面部件)。

vtkResliceCursorWidget集成了一个vtkResliceCursorThickLineRepresentation对象用户交互调整切分平面,一个vtkResliceCursor对象根据用户设置的切分平面来进行图像切分;而vtkImagePlaneWidget类同样根据vtkResliceCursor对象根据用户设置的切分平面利用内部定义的vtkImageReslice类来计算切分图像并进行显示。本实例仅仅是实现了一个四视图的Demo程序,还存在许多需要完善的地方,各位读者有兴趣的话,可以在此基础上进行修改和完善。

本文转载自:http://lanxicy.com/read/9c62e9a527573f2e0f2d60d8.html

共有 人打赏支持
天蚕宝衣
粉丝 18
博文 236
码字总数 178069
作品 0
天津
c语言编程软件有哪些 Win7下用哪种C语言编译器

C语言是一门历史很长的编程语言,其编译器和开发工具也多种多样,其开发工具包括编译器,现举几个开发工具供大家选择,当然也要根据自己的操作系统来选择适合自己的开发工具 好多刚开始接触c...

mini92 ⋅ 04/20 ⋅ 0

VS2010/MFC编程入门教程之目录和总结(鸡啄米)

鸡啄米的这套VS2010/MFC编程入门教程到此就全部完成了,虽然有些内容还未涉及到,但帮助大家进行VS2010/MFC的入门学习业已足够。以此教程的知识为基础,学习VS2010/MFC较为深入的内容已非难事...

weixin_40647819 ⋅ 05/23 ⋅ 0

大神有话说之c++,还在迷茫的朋友可以来看一下

C++ 是一种中级语言,它是由 Bjarne Stroustrup 于 1979 年在贝尔实验室开始设计开发的。C++ 进一步扩充和完善了 C 语言,是一种面向对象的程序设计语言。C++ 可运行于多种平台上,如 Window...

悟空_b201 ⋅ 05/30 ⋅ 0

C语言/C++编程学习强势之处的体现

C语言是面向过程的,而C++是面向对象的 C和C++的区别: C是一个结构化语言,它的重点在于算法和数据结构。C程序的设计首要考虑的是如何通过一个过程,对输入(或环境条件)进行运算处理得到...

小辰带你看世界 ⋅ 05/12 ⋅ 0

什么是 C 和 C ++ 标准库?

简要介绍编写C/C ++应用程序的领域,标准库的作用以及它是如何在各种操作系统中实现的。 我已经接触C++一段时间了,一开始就让我感到疑惑的是其内部结构:我所使用的内核函数和类从何而来? ...

oschina ⋅ 04/10 ⋅ 0

Java程序员如何高效而优雅地入门C++

Java程序员如何高效而优雅地入门Cpp,由于工作需要,需要用C++写一些模块。关于C++ 的知识结构,虽说我有过快速学习很多新语言的经验,但对于C++ 我也算是老手,但也还需要心生敬畏,本文会从...

小欣妹妹 ⋅ 04/23 ⋅ 0

MFC功能扩展控件BCGSuite for MFC发布v27.1|附下载

BCGSuite for MFC是一款Visual Studio 2008/2010/2012/2013 的MFC功能扩展控件。虽然Visual Studio 2008中包含的新版MFC是基于BCGControlBar Pro技术的,但某些典型的控件如网格、日历、编辑...

Miss_Hello_World ⋅ 04/26 ⋅ 0

C语言/C++编程新手学习常见问题

C语言是面向过程的,而C++是面向对象的 C和C++的区别: C是一个结构化语言,它的重点在于算法和数据结构。C程序的设计首要考虑的是如何通过一个过程,对输入(或环境条件)进行运算处理得到...

小辰带你看世界 ⋅ 05/11 ⋅ 0

公开课-C++学习路线实战导引:从0开始到操作系统内核开发

公开课观看办法: 加入到51CTO学院C++交流群 431187655 在群中直播 课程简介 从整个IT行业角度出发, C/C++技术定位于后端服务与系统级软件研发工作,这意味着C/C++的从业人员应当精通从win...

夏曹俊 ⋅ 06/13 ⋅ 0

C语言/C++程序员编程学习自信心曲线图

C语言是面向过程的,而C++是面向对象的 C和C++的区别: C是一个结构化语言,它的重点在于算法和数据结构。C程序的设计首要考虑的是如何通过一个过程,对输入(或环境条件)进行运算处理得到...

小辰带你看世界 ⋅ 05/10 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Springboot2 之 Spring Data Redis 实现消息队列——发布/订阅模式

一般来说,消息队列有两种场景,一种是发布者订阅者模式,一种是生产者消费者模式,这里利用redis消息“发布/订阅”来简单实现订阅者模式。 实现之前先过过 redis 发布订阅的一些基础概念和操...

Simonton ⋅ 18分钟前 ⋅ 0

error:Could not find gradle

一.更新Android Studio后打开Project,报如下错误: Error: Could not find com.android.tools.build:gradle:2.2.1. Searched in the following locations: file:/D:/software/android/andro......

Yao--靠自己 ⋅ 昨天 ⋅ 0

Spring boot 项目打包及引入本地jar包

Spring Boot 项目打包以及引入本地Jar包 [TOC] 上篇文章提到 Maven 项目添加本地jar包的三种方式 ,本篇文章记录下在实际项目中的应用。 spring boot 打包方式 我们知道,传统应用可以将程序...

Os_yxguang ⋅ 昨天 ⋅ 0

常见数据结构(二)-树(二叉树,红黑树,B树)

本文介绍数据结构中几种常见的树:二分查找树,2-3树,红黑树,B树 写在前面 本文所有图片均截图自coursera上普林斯顿的课程《Algorithms, Part I》中的Slides 相关命题的证明可参考《算法(第...

浮躁的码农 ⋅ 昨天 ⋅ 0

android -------- 混淆打包报错 (warning - InnerClass ...)

最近做Android混淆打包遇到一些问题,Android Sdutio 3.1 版本打包的 错误如下: Android studio warning - InnerClass annotations are missing corresponding EnclosingMember annotation......

切切歆语 ⋅ 昨天 ⋅ 0

eclipse酷炫大法之设置主题、皮肤

eclipse酷炫大法 目前两款不错的eclipse 1.系统设置 Window->Preferences->General->Appearance 2.Eclipse Marketplace下载【推荐】 Help->Eclipse Marketplace->搜索‘theme’进行安装 比如......

anlve ⋅ 昨天 ⋅ 0

vim编辑模式、vim命令模式、vim实践

vim编辑模式 编辑模式用来输入或修改文本内容,编辑模式除了Esc外其他键几乎都是输入 如何进入编辑模式 一般模式输入以下按键,均可进入编辑模式,左下角提示 insert(中文为插入) 字样 i ...

蛋黄Yolks ⋅ 昨天 ⋅ 0

大数据入门基础:SSH介绍

什么是ssh 简单说,SSH是一种网络协议,用于计算机之间的加密登录。 如果一个用户从本地计算机,使用SSH协议登录另一台远程计算机,我们就可以认为,这种登录是安全的,即使被中途截获,密码...

董黎明 ⋅ 昨天 ⋅ 0

web3j教程

web3j是一个轻量级、高度模块化、响应式、类型安全的Java和Android类库提供丰富API,用于处理以太坊智能合约及与以太坊网络上的客户端(节点)进行集成。 汇智网最新发布的web3j教程,详细讲解...

汇智网教程 ⋅ 昨天 ⋅ 0

谷歌:安全问题机制并不如你想象中安全

腾讯科技讯 5月25日,如今的你或许已经对许多网站所使用的“安全问题机制”习以为常了,但你真的认为包括“你第一个宠物的名字是什么?”这些问题能够保障你的帐户安全吗? 根据谷歌(微博)安...

问题终结者 ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部